JSPM

  • Created
  • Published
  • Downloads 20800
  • Score
    100M100P100Q159926F
  • License MIT

Library to store state in browser URL, includes hook for NextJS, hook for pure React, and low level helpers.

Package Exports

  • state-in-url

Readme

English | 한국어 | 简体中文

state-in-url

Library logo
Easily share state between unrelated React components, with IDE autocomplete and TS validation. Without any hasssle or boilerplate.

npm Tests Codacy Badge Commitizen friendly npm bundle size (minified + gzip)

Don't hesitate to open an issue if you found a bug

Demo-gif

DEMO | Demo on Codesandbox


Add a ⭐️ to support the project!


Why use state-in-url?

state-in-url Simple state management with URL synchronization.

Use cases

  • 🙃 Share the state between different components without changing url, good as alternative to signals and other state management tools
  • 🔗 Shareable URLs with full application state
  • 🔄 Easy state persistence across page reloads
  • 🧠 Sync data between unrelated client components
  • 🧮 Store unsaved user forms in URL

Features

  • 🧩 Simple: No providers, reducers, boilerplate or new concepts
  • 📘 Typescript support and type Safety: Preserves data types and structure, enhances developer experience with IDE suggestions, strong typing and JSDoc comments
  • ⚛️ Framework Flexibility: Separate hooks for Next.js and React.js applications, and functions for pure JS
  • Well tested: Unit tests and Playwright tests
  • Fast: Minimal rerenders
  • 🪶 Lightweight: Zero dependencies for a smaller footprint

Table of content

installation

1. Install package

# npm
npm install --save state-in-url
# yarn
yarn add state-in-url
# pnpm
pnpm add state-in-url

2. Edit tsconfig.json

set "moduleResolution": "Node16" or "moduleResolution": "NodeNext" in your tsconfig.json

useUrlState hook for Next.js

Docs

useUrlState is a custom React hook for Next.js applications that make communication between client components easy. It allows you to share any complex state and sync it with the URL search parameters, providing a way to persist state across page reloads and share application state via URLs.

Usage examples

Basic

  1. Define state shape

    // countState.ts
    // State shape should be stored in a constant, don't pass an object directly
    export const countState: CountState = { count: 0 }
    
    type CountState = { count: number }
  2. Import it and use

'use client'
import { useUrlState } from 'state-in-url';

import { countState } from './countState';

function MyComponent() {
  // for use searchParams from server component
  // e.g. export default async function Home({ searchParams }: { searchParams: object }) {
  // const { state, updateState, updateUrl } = useUrlState(countState, searchParams);
  const { state, updateState, updateUrl } = useUrlState(countState);

  // won't let you to accidently mutate state directly, requires TS
  // state.count = 2 // <- error

  return (
    <div>
      <p>Count: {state.count}</p>

      <button onClick={() => updateUrl({ count: state.count + 1 }), { replace: true }}>
        Increment (Update URL)
      </button>

        // same api as React.useState
      <button onClick={() => updateState(currState => ({...currState, count: currState.count + 1 }) )}>
        Increment (Local Only)
      </button>
      <button onClick={() => updateUrl()}>
        Sync changes to url
        // Or don't sync it and just share state
      </button>

      <button onClick={() => updateUrl(state)}>
        Reset
      </button>
    </div>
  )
}

With complex state shape

interface UserSettings {
  theme: 'light' | 'dark';
  fontSize: number;
  notifications: boolean;
}

export const userSettings: UserSettings {
  theme: 'light',
  fontSize: 16,
  notifications: true,
}
'use client'
import { useUrlState } from 'state-in-url';

import { userSettings } from './userSettings';

function SettingsComponent() {
  // `state` will infer from UserSettings type!
  const { state, updateUrl } = useUrlState(userSettings);

  const toggleTheme = () => {
    updateUrl(current => ({
      ...current,
      theme: current.theme === 'light' ? 'dark' : 'light',
    }));
  };

  // sync state to url when idle
  const timer = React.useRef(0 as unknown as NodeJS.Timeout);
  React.useEffect(() => {
    clearTimeout(timer.current);
    timer.current = setTimeout(() => {
      // will compare state by content not by reference and fire update only for new values
      updateUrl(state);
    }, 500);

    return () => {
      clearTimeout(timer.current);
    };
  }, [state, updateUrl]);

  return (
    <div>
      <h2>User Settings</h2>
      <p>Theme: {state.theme}</p>
      <p>Font Size: {state.fontSize}px</p>
      <button onClick={toggleTheme}>Toggle Theme</button>
      {/* Other UI elements to update other settings */}
    </div>
  );
}
...

// Other component
function Component() {
  const { state } = useUrlState(defaultSettings);

  return (
    <div>
      <p>Notifications is {state.notifications ? 'On' : 'Off'}</p>
    </div>
  )
}

Auto sync state

  const timer = React.useRef(0 as unknown as NodeJS.Timeout);
  React.useEffect(() => {
    clearTimeout(timer.current);
    timer.current = setTimeout(() => {
      // will compare state by content not by reference and fire update only for new values
      updateUrl(state);
    }, 500);

    return () => {
      clearTimeout(timer.current);
    };
  }, [state, updateUrl]);
'use client'
import { useUrlState } from 'state-in-url';

const someObj = {};

function SettingsComponent() {
  const { state, updateUrl, updateState } = useUrlState<object>(someObj);
}

useUrlEncode hook for React.js

Docs

encodeState and decodeState helpers

Docs

encode and decode helpers

Docs

Best Practices

  • Define your state shape as a constant to ensure consistency
  • Use TypeScript for enhanced type safety and autocomplete
  • Avoid storing sensitive information in URL parameters
  • Use updateState for frequent updates and updateUrl to sync changes to url
  • Use this extension for readable TS errors

Gothas

  1. Can pass only serializable values, Function, BigInt or Symbol won't work, probably things like ArrayBuffer neither.
  2. Vercel servers limit size of headers (query string and other stuff) to 14KB, so keep your URL state under ~5000 words. https://vercel.com/docs/errors/URL_TOO_LONG

Run locally

Clone this repo, run npm install and

npm run dev

Go to localhost:3000

Contact & Support

  • Create a GitHub issue for bug reports, feature requests, or questions

Changelog

License

This project is licensed under the MIT license.

Inspiration

Using URL to store state in Vue

Storing state in the URL

NextJS useSearchParams