Estado tipado, vivendo na URL
useUrlState é o estado React que se grava na string de consulta. Objetos, arrays e datas mantêm seus tipos, cada estado é um link compartilhável e sobrevive a recarregamentos — sem providers, sem boilerplate.
- ~2 KB em gzip
- zero dependências
- TypeScript-first
- Next.js / react-router / Remix
- MIT
npm i state-in-urluseUrlState — ao vivo com next.js
Digite abaixo — veja a URL acender
Lê da URL — sem props, sem context, os tipos e a estrutura são preservados
{name: stringage: undefinedundefinedagree_to_terms: falsebooleantags: []}
A mesma API, três routers
Início rápido
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 } }[];
};'use client';
import { useUrlState } from 'state-in-url/next';
import { form } from './form';
export const ComponentA = () => {
// see docs for all possible params https://github.com/asmyshlyaev177/state-in-url/tree/master/packages/urlstate/next/useUrlState
// useHistory: false to update sp on server component
const { urlState, setState, setUrl } = useUrlState(form, { useHistory: true });
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>
</>
};'use client';
import { useUrlState } from 'state-in-url/next';
import { form } from './form';
// "searchParams" used to pass params from Server Components
export const ComponentB = ({ searchParams }: { searchParams?: object }) => {
const { urlState } = useUrlState(form, { searchParams });
// will be defaultValue from `form` if not in url, no need to check
return <div>name: {urlState.name}</div>
};'use client';
import React from 'react';
import { useUrlState } from 'state-in-url/next';
const form: Form={
name: '',
age: undefined,
agree_to_terms: false,
tags: [],
};
type Form = {
name: string;
age?: number;
agree_to_terms: boolean;
tags: {id: string; value: {text: string; time: Date } }[];
};
export const useFormState = ({ searchParams }: { searchParams?: object }) => {
const { urlState, setUrl: setUrlBase, reset } = useUrlState(form, {
searchParams,
});
// 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 };
};Usando um agente de codificação de IA?
Os agentes erram as mesmas duas coisas aqui, todas as vezes. Eles definem a forma do estado com interface, que a restrição JSONCompatible rejeita de imediato. E eles constroem o objeto de estado padrão dentro do componente, o que quebra o compartilhamento em silêncio — ele é indexado pela identidade do objeto, então nada gera erro, os dois componentes simplesmente param de se ver.
Então o pacote inclui seis SKILL.md arquivos. Seu agente carrega um sob demanda por meio do TanStack Intent, e eles são versionados com a biblioteca e não com esta página.
npx @tanstack/intent@latest installExecute uma vez em um projeto que já tenha state-in-url instalado. Seu agente então encontra as habilidades em node_modules/state-in-url/skills/.
feature-state-hookDefinir o estado e envolver o useUrlState em um hook com escopo de funcionalidadeinput-handlingCampos de texto, sliders, qualquer coisa que muda rápidonextjs-ssrApp Router: encaminhamento de searchParams, Proxy para layoutsreact-router-remix-setupConfiguração do React Router v6/v7 ou Remix v2form-library-integrationCombinando com react-hook-form (ou formik)shared-state-no-urluseSharedState — compartilhar sem tocar na URL
As fontes estão no GitHub. Um agente que não consegue carregar as habilidades do Intent deve ler llms.txt em vez disso — as mesmas regras, condensadas em um arquivo.
Por que state-in-url?
Existem bibliotecas de estado na URL, mas a maioria é complicada de configurar ou limitada no que pode armazenar. state-in-url pretende ser a que simplesmente funciona: uma API que espelha React.useState, com a URL como armazenamento.
Armazene estado sem boilerplate, construa links profundos e compartilhe dados entre componentes de cliente não relacionados — sem necessidade de provider. A estrutura e os tipos são preservados de ponta a ponta: um Date entra, um Date sai.
Construído com test-first, com suítes unitárias e e2e entre navegadores executando a cada commit.
Next.js: sem limite de Suspense
O hook nunca chama useSearchParams, então um componente que o usa não precisa ser envolvido em Suspense e não exclui sua página da pré-renderização — PPR e cacheComponents incluídos. Ele lê a URL diretamente e acompanha cada alteração posterior, incluindo um history.pushState de um código que nada sabe sobre ele.
Não usa Next.js ou react-router?
Os helpers encodeState / decodeState funcionam com qualquer framework ou JS puro — os hooks são uma conveniência por cima.
Confira a página no GitHub — uma estrela ajuda muito.
Compartilhe com outros desenvolvedores

