État typé, vivant dans l'URL

useUrlState est l'état React qui s'écrit lui-même dans la chaîne de requête. Les objets, les tableaux et les dates conservent leurs types, chaque état est un lien partageable et survit aux rechargements — sans providers, sans code répétitif.

  • ~2 KB en gzip
  • zéro dépendance
  • TypeScript-first
  • Next.js / react-router / Remix
  • MIT
npm i state-in-url

useUrlState — en direct avec remix.js

Tapez ci-dessous — regardez l'URL s'allumer

Premier composant client
Autre composant client

Lit depuis l'URL — sans props, sans context, les types et la structure sont préservés

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

La même API, trois routeurs

Démarrage rapide

1. Définissez l’état
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. Enveloppez-le dans un hook réutilisable
useFormState
import { useUrlState } from 'state-in-url/remix';
import { form } from './form';

// One hook per feature - the whole API for this state
export const useFormState = () => useUrlState(form);
3. Utilisez-le dans n’importe quel composant — tous le partagent
ComponentA
import { useFormState } from './useFormState';

export const ComponentA = () => {
  // see docs for all possible params https://github.com/asmyshlyaev177/state-in-url/tree/master/packages/urlstate/remix/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>
    </>
};
ComponentB
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>
};
4. Faites grandir le hook quand il en faut plus
useFormState - extended
import React from 'react';
import { useUrlState } from 'state-in-url/remix';
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

Vous cherchez une alternative à nuqs ? Les deux gardent un état typé dans la query string ; elles diffèrent par la configuration requise et par ce qu’une valeur peut être.

Quoistate-in-urlnuqs
Mise en placeAucune — importez le hook et c’est partiUn composant adaptateur enveloppe l’application
Forme de l’étatUn objet typé, comme React.useStateValeurs par clé, un parseur déclaré pour chacune
Réutilisation entre composantsEnveloppez le hook une fois — chaque composant partage l’état, sans propsVous extrayez votre propre hook autour de la table de parseurs
Objets et tableaux imbriquésIntégré — structure et types préservésParseur JSON plus votre propre validateur
DatesPréservées automatiquementParseur intégré, déclaré par clé
Taille, import complet~2,9 Ko gzip~6,7 Ko gzip
Dépendances au runtimeAucuneUne
RouteursNext.js, React Router v6/v7, Remix, helpers pour JS purNext.js, React Router, Remix, TanStack Router, React pur

Tailles : import de la bibliothèque entière, esbuild minify + gzip, mesuré en août 2026 face à nuqs 2.10.1.

nuqs est une bonne bibliothèque — choisissez-la pour un query param lisible par valeur, ou si vous êtes sur TanStack Router. Choisissez state-in-url pour un objet typé complet dans l’URL, sans configuration.

Lisez la comparaison complète — la même fonctionnalité dans les deux, et comment migrer

Pourquoi state-in-url ?

Il existe des bibliothèques d'état dans l'URL, mais la plupart sont soit fastidieuses à configurer, soit limitées dans ce qu'elles peuvent stocker. state-in-url vise à être celle qui fonctionne tout simplement : une API qui reflète React.useState, avec l'URL comme stockage.

Stockez l'état sans code répétitif, construisez des liens profonds et partagez des données entre composants client sans rapport — aucun provider nécessaire. La structure et les types sont préservés de bout en bout : un Date entre, une Date sort.

Construit en test-first, avec des suites unitaires et e2e inter-navigateurs qui s'exécutent à chaque commit.

Next.js : aucune limite Suspense

Le hook n'appelle jamais useSearchParams, donc un composant qui l'utilise n'a pas besoin d'être enveloppé dans Suspense et n'exclut pas sa page du pré-rendu — PPR et cacheComponents inclus. Il lit l'URL directement et suit chaque changement ultérieur, y compris un history.pushState issu d'un code qui n'en sait rien.

Pas sur Next.js ou react-router ?

Les helpers encodeState / decodeState fonctionnent avec n'importe quel framework ou du JS pur — les hooks sont une commodité par-dessus.

Consultez la page GitHub — une étoile fait beaucoup.

Partagez-le avec d'autres développeurs

Uneed Embed Badge