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-url

useUrlState — en vivo con react-router

Escribe abajo — observa cómo se enciende la URL

Primer componente de cliente
Otro componente de cliente

Lee desde la URL — sin props, sin context, los tipos y la estructura se conservan

{
name: string
age: undefinedundefined
agree_to_terms: falseboolean
tags: []
}

La misma API, tres routers

Inicio rápido

1. Define el estado
state
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 } }[];
};
2. Úsalo en cualquier componente
ComponentA
import { useUrlState } from 'state-in-url/react-router';
// for react-router v6
// import { useUrlState } from 'state-in-url/react-router6';
import { form } from './form';

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 } = useUrlState(form);

  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>
    </>
};
ComponentB
import { useUrlState } from 'state-in-url/react-router';
import { form } from './form';

export const ComponentB = () => {
  const { urlState } = useUrlState(form);

// will be defaultValue from `form` if not in url, no need to check

  return <div>name: {urlState.name}</div>
};
3. Crea un hook reutilizable para una parte del estado
useFormState - custom hook
import React from 'react';
import { useUrlState } from 'state-in-url/react-router';

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 = () => {
  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 };
};

¿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

Uneed Embed Badge