State có kiểu, sống trong URL
useUrlState là state React tự ghi chính nó vào chuỗi truy vấn. Object, array và ngày giữ nguyên kiểu, mọi state là một liên kết có thể chia sẻ và tồn tại qua các lần tải lại — không provider, không boilerplate.
- ~2 KB nén gzip
- không phụ thuộc
- TypeScript-first
- Next.js / react-router / Remix
- MIT
npm i state-in-urluseUrlState — trực tiếp với react-router
Gõ bên dưới — xem URL sáng lên
Đọc từ URL — không props, không context, kiểu và cấu trúc được giữ nguyên
{name: stringage: undefinedundefinedagree_to_terms: falsebooleantags: []}
Cùng một API, ba router
Bắt đầu nhanh
export const form: Form = {
name: '',
age: undefined,
agree_to_terms: false,
tags: [],
};
// use `Type` not `Interface`!
type Form = {
name: string;
age?: number;
agree_to_terms: boolean;
tags: { id: string; value: { text: string; time: Date } }[];
};import { useUrlState } from 'state-in-url/react-router';
// for react-router v6
// import { useUrlState } from 'state-in-url/react-router6';
import { form } from './form';
// One hook per feature - the whole API for this state
export const useFormState = () => useUrlState(form);import { useFormState } from './useFormState';
export const ComponentA = () => {
// see docs for all possible params https://github.com/asmyshlyaev177/state-in-url/tree/master/packages/urlstate/react-router/useUrlState
const { urlState, setUrl, setState } = useFormState();
return <>
<input
id="name"
value={urlState.name}
onChange={(ev) => setUrl({ name: ev.target.value })}
/>
// OR can update state immediately but sync change to url as needed
<input
value={urlState.name}
onChange={(ev) => { setState(curr => ({ ...curr, name: ev.target.value })) }}
onBlur={() => setUrl()}
/>
<button onClick={() => setUrl((curr, initial) => initial)}>
Reset
</button>
</>
};import { useFormState } from './useFormState';
export const ComponentB = () => {
// same state as ComponentA - no props, no context
const { urlState } = useFormState();
// will be defaultValue from `form` if not in url, no need to check
return <div>name: {urlState.name}</div>
};import React from 'react';
import { useUrlState } from 'state-in-url/react-router';
import { form } from './form';
export const useFormState = () => {
const { urlState, setUrl: setUrlBase, reset } = useUrlState(form);
// first navigation will push new history entry
// all following will just replace that entry
// this way will have history with only 2 entries - ['/url', '/url?key=param']
const replace = React.useRef(false);
const setUrl = React.useCallback((
state: Parameters<typeof setUrlBase>[0],
opts?: Parameters<typeof setUrlBase>[1]
) => {
setUrlBase(state, { replace: replace.current, ...opts });
replace.current = true;
}, [setUrlBase]);
return { urlState, setUrl, resetUrl: reset };
};state-in-url vs nuqs
Đang tìm một lựa chọn thay thế nuqs? Cả hai đều lưu state có kiểu trong query string; khác nhau ở lượng cấu hình cần thiết và giá trị có thể là gì.
| Tiêu chí | state-in-url | nuqs |
|---|---|---|
| Cài đặt | Không cần — import hook là dùng được | Component adapter bọc quanh ứng dụng |
| Hình dạng state | Một object có kiểu, giống React.useState | Giá trị theo từng khóa, mỗi khóa khai báo một parser |
| Tái sử dụng giữa các component | Bọc hook một lần — mọi component chia sẻ state, không cần props | Bạn tự tách hook quanh bộ parser |
| Object và mảng lồng nhau | Có sẵn — giữ nguyên cấu trúc và kiểu | Parser JSON cộng thêm validator tự viết |
| Ngày tháng | Giữ nguyên tự động | Parser có sẵn, khai báo theo từng khóa |
| Kích thước, import toàn bộ | ~2.9 KB gzip | ~6.7 KB gzip |
| Phụ thuộc runtime | Không | Một |
| Router | Next.js, React Router v6/v7, Remix, helper cho JS thuần | Next.js, React Router, Remix, TanStack Router, React thuần |
Kích thước: import cả thư viện, esbuild minify + gzip, đo tháng 8/2026 với nuqs 2.10.1.
nuqs là một thư viện tốt — hãy chọn nó khi bạn muốn mỗi giá trị là một query param dễ đọc, hoặc đang dùng TanStack Router. Chọn state-in-url khi bạn muốn cả một object có kiểu nằm trong URL mà không cần cấu hình.
Đọc bản so sánh đầy đủ — cùng một tính năng viết bằng cả hai, và cách di chuyển
Tại sao là state-in-url?
Các thư viện state trong URL đã tồn tại, nhưng hầu hết hoặc rườm rà khi thiết lập, hoặc hạn chế về thứ có thể lưu. state-in-url hướng tới việc đơn giản là hoạt động: một API phản chiếu React.useState, với URL làm nơi lưu trữ.
Lưu state không cần boilerplate, xây deep link và chia sẻ dữ liệu giữa các client component không liên quan — không cần provider. Cấu trúc và kiểu được giữ nguyên từ đầu đến cuối: một Date vào, một Date ra.
Được xây dựng theo test-first, với bộ test đơn vị và e2e đa trình duyệt chạy trên mỗi commit.
Next.js: không cần ranh giới Suspense
Hook không bao giờ gọi useSearchParams, nên component dùng nó không cần bọc trong Suspense và không khiến trang bị loại khỏi prerendering — bao gồm PPR và cacheComponents . Nó đọc URL trực tiếp và theo dõi mọi thay đổi sau đó, bao gồm cả history.pushState từ code không biết gì về nó.
Không dùng Next.js hoặc react-router?
Các helper encodeState / decodeState hoạt động với bất kỳ framework hoặc JS thuần — các hook là sự tiện lợi bên trên.
Hãy xem trang GitHub — một star giúp ích rất nhiều.
Chia sẻ với các nhà phát triển khác

