Estado tipado, viviendo en la URL
useUrlState es el estado de React que se escribe a sí mismo en la cadena de consulta. Los objetos, los arrays y las fechas conservan sus tipos, cada estado es un enlace compartible y sobrevive a las recargas, sin providers ni código repetitivo.
- ~2 KB en gzip
- cero dependencias
- TypeScript-first
- Next.js / react-router / Remix
- MIT
npm i state-in-urluseUrlState — en vivo con next.js
Escribe abajo — observa cómo se enciende la URL
Lee desde la URL — sin props, sin context, los tipos y la estructura se conservan
{name: stringage: undefinedundefinedagree_to_terms: falsebooleantags: []}
La misma API, tres routers
Inicio 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 };
};¿Usas un agente de codificación con IA?
Los agentes se equivocan siempre en las mismas dos cosas aquí. Escriben la forma del estado con interface, que la restricción JSONCompatible rechaza de plano. Y construyen el objeto de estado por defecto dentro del componente, lo que rompe el uso compartido en silencio: se indexa por identidad del objeto, así que nada da error, los dos componentes simplemente dejan de verse.
Así que el paquete incluye seis SKILL.md archivos. Tu agente carga uno bajo demanda a través de TanStack Intent, y se versionan con la biblioteca y no con esta página.
npx @tanstack/intent@latest installEjecuta una vez en un proyecto que ya tenga state-in-url instalado. Tu agente encontrará entonces las habilidades en node_modules/state-in-url/skills/.
feature-state-hookDefinir el estado y envolver useUrlState en un hook de ámbito de funcionalidadinput-handlingCampos de texto, deslizadores, cualquier cosa que cambie rápidonextjs-ssrApp Router: reenvío de searchParams, Proxy para layoutsreact-router-remix-setupConfiguración de React Router v6/v7 o Remix v2form-library-integrationCombinación con react-hook-form (o formik)shared-state-no-urluseSharedState — compartir sin tocar la URL
Las fuentes están en GitHub. Un agente que no pueda cargar las habilidades de Intent debería leer llms.txt en su lugar — las mismas reglas, condensadas en un archivo.
¿Por qué state-in-url?
Existen bibliotecas de estado en la URL, pero la mayoría son engorrosas de configurar o limitadas en lo que pueden almacenar. state-in-url aspira a ser la que simplemente funciona: una API que imita React.useState, con la URL como almacén.
Guarda estado sin código repetitivo, construye enlaces profundos y comparte datos entre componentes de cliente no relacionados, sin necesidad de provider. La estructura y los tipos se conservan de extremo a extremo: un Date entra, un Date sale.
Construido con test-first, con suites unitarias y e2e entre navegadores ejecutándose en cada commit.
Next.js: sin límite de Suspense
El hook nunca llama a useSearchParams, por lo que un componente que lo usa no necesita envolverse en Suspense y no excluye su página del prerenderizado: PPR y cacheComponents incluidos. Lee la URL directamente y sigue cada cambio posterior, incluido un history.pushState desde código que no sabe nada de él.
¿No usas Next.js o react-router?
Los helpers encodeState / decodeState funcionan con cualquier framework o JS puro: los hooks son una comodidad encima.
Échale un vistazo a la página de GitHub : una estrella ayuda mucho.
Compártelo con otros desarrolladores

