Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up

Store any user state in query parameters; imagine JSON in a browser URL, while keeping types and structure of data, e.g.numbers will be decoded as numbers not strings. With TS validation. Shared state and URL state sync without any hassle or boilerplate. Supports Next.js@14-15, and react-router@6-7.

License

NotificationsYou must be signed in to change notification settings

asmyshlyaev177/state-in-url

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

English |한국어 |简体中文

state-in-url logo

State in url

npmnpm bundle size (minified + gzip)Codacy Badge

TestsCodacy BadgeCommitizen friendlysemantic-release: angular

OpenSSF ScorecardOpenSSF Best Practiceslicense

Don't hesitate to open an issue if you found a bug, or for requesting features

Demo-gif

Demo

Demo |Mirror link

URI size limitation,up to 12KB is safe


Add a andfollow me to support the project!

Will appreciate you feedback/opinion ondiscussions

Share if it useful for you.X.comLinkedInFBVK


Just Show Me Code!

Why usestate-in-url?

Store any user state in query parameters; imagine JSON in a browser URL. All of it with keeping types and structure of data, e.g. numbers will be decoded as numbers not strings, dates as dates, etc, objects and arrays supported.Dead simple, fast, and with static Typescript validation. Deep links, aka URL synchronization, made easy.

ContainsuseUrlState hook for Next.js and react-router, and helpers for anything else on JS.Since modern browsers support huge URLs and users don't care about query strings (it is a select all and copy/past workflow).

Time to use query string for state management, as it was originally intended.This library does all mundane stuff for you.

This library is a good alternative for NUQS.

Use cases

  • Store unsaved user forms or page filters in URL
  • Sycn URL with React state
  • Just sync data between unrelated client components without touching URI
  • Shareable URLs with application state (Deep linking, URL state synchronization)
  • Easy state persistence across page reloads

Features

  • 🧩Simple: No providers, reducers, boilerplate or new concepts, API similar toReact.useState
  • 📘Typescript validation/autocomplete: State is just an object, automatic static validation in IDE/tests according to Typescript definition
  • Complex data: Nested objects, dates and arrays, works same as JSON, but in URL
  • Default values: Giving you default values if parameter not in url
  • Organized: All possible values defined at start, protect you from getting non existing key
  • compatible: Will keep 3rd party query params as is
  • flexible: Can use more than 1 state objects on the same page, just use different keys
  • Fast: Minimal rerenders, around1ms to encode and decode big object
  • Server Side Rendering: Can use it in Server Components, Next.js 14 and 15 are supported
  • Lightweight: Zero dependencies, library less than 2KB
  • DX: Good developer experience, documentation, JSDoc comments, and examples
  • Framework Flexibility: Hooks forNext.js andreact-router, helpers to use it with other frameworks or pure JS
  • Well tested:Unit tests and Playwright tests for Chrome/Firefox/Safari
  • Permissive license: MIT

Table of content

installation

1. Install package

# npmnpm install --save state-in-url# yarnyarn add state-in-url# pnpmpnpm add state-in-url

2. Edit tsconfig.json

Intsconfig.json incompilerOptions set"moduleResolution": "Bundler", or"moduleResolution": "Node16", or"moduleResolution": "NodeNext".Possibly need to set"module": "ES2022", or"module": "ESNext"

useUrlState

Main hook that takes initial state as parameter and returns state object, callback to update url, and callback to update only state.All components that use the samestate object are automatically synchronized.

useUrlState hook for Next.js

Full API Docs

React-Router example

Usage examples

Basic
  1. Define state shape with default values
// userState.ts// Only parameters with value different from default will go to the url.exportconstuserState:UserState={name:'',age:0}// use `Type` not `Interface`!typeUserState={name:string,age:number}
  1. Import it and use
'use client'import{useUrlState}from'state-in-url/next';import{userState}from'./userState';functionMyComponent(){// can pass `replace` arg, it's control will `setUrl` will use `rounter.push` or `router.replace`, default replace=true// can pass `searchParams` from server components, pass `useHistory: false` if you need to fetch smt in the server componentconst{ urlState, setUrl, setState, reset}=useUrlState(userState);return(<div>// urlState.name will return default value from `userState` if url empty<inputvalue={urlState.name}// same api as React.useState, e.g. setUrl(currVal => currVal + 1)onChange={(ev)=>setUrl({name:ev.target.value})}/><inputvalue={urlState.age}onChange={(ev)=>setUrl({age:+ev.target.value})}/><inputvalue={urlState.name}onChange={(ev)=>{setState(curr=>({ ...curr,name:ev.target.value}))}}// Can update state immediately but sync change to url as neededonBlur={()=>setUrl()}/><buttononClick={reset}>Reset</button></div>)}
Custom hook to work with slice of state conveniently
Example
'use client';importReactfrom'react';import{useUrlState}from'state-in-url/next';constform:Form={name:'',age:undefined,agree_to_terms:false,tags:[],};typeForm={name:string;age?:number;agree_to_terms:boolean;tags:{id:string;value:{text:string;time:Date}}[];};exportconstuseFormState=({ 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']constreplace=React.useRef(false);constsetUrl=React.useCallback((state:Parameters<typeofsetUrlBase>[0],opts?:Parameters<typeofsetUrlBase>[1])=>{setUrlBase(state,{replace:replace.current, ...opts});replace.current=true;},[setUrlBase]);return{ urlState, setUrl,resetUrl:reset};};

With complex state shape
Example
exportconstform:Form={name:'',age:undefined,agree_to_terms:false,tags:[],};typeForm={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';functionTagsComponent(){// `urlState` will infer from Form type!const{ urlState, setUrl}=useUrlState(form);constonChangeTags=React.useCallback((tag:(typeoftags)[number])=>{setUrl((curr)=>({        ...curr,tags:curr.tags.find((t)=>t.id===tag.id)          ?curr.tags.filter((t)=>t.id!==tag.id)          :curr.tags.concat(tag),}));},[setUrl],);return(<div><Fieldtext="Tags"><divclassName="flex flex-wrap gap-2">{tags.map((tag)=>(<Tagactive={!!urlState.tags.find((t)=>t.id===tag.id)}text={tag.value.text}onClick={()=>onChangeTags(tag)}key={tag.id}/>))}</div></Field></div>);}consttags=[{id:'1',value:{text:'React.js',time:newDate('2024-07-17T04:53:17.000Z')},},{id:'2',value:{text:'Next.js',time:newDate('2024-07-18T04:53:17.000Z')},},{id:'3',value:{text:'TailwindCSS',time:newDate('2024-07-19T04:53:17.000Z')},},];

Demo page example code

Update state only and sync to URL manually
Example
consttimer=React.useRef(0asunknownasNodeJS.Timeout);React.useEffect(()=>{clearTimeout(timer.current);timer.current=setTimeout(()=>{// will compare state by content not by reference and fire update only for new valuessetUrl(urlState);},500);return()=>{clearTimeout(timer.current);};},[urlState,setUrl]);

Syncing stateonBlur will be more aligned with real world usage.

<inputonBlur={()=>updateUrl()} .../>
With server side rendering
Example
exportdefaultasyncfunctionHome({ searchParams}:{searchParams:object}){return(<FormsearchParams={searchParams}/>)}// Form.tsx'use client'importReactfrom'react';import{useUrlState}from'state-in-url/next';import{form}from'./form';constForm=({ searchParams}:{searchParams:object})=>{const{ urlState, setState, setUrl}=useUrlState(form,{ searchParams});}
Using hook inlayout component
Example That a tricky part, since nextjs with app router doesn't allow to access searchParams from server side. There is workaround with using middleware, but it isn't pretty and can stop working after nextjs update.
// add to appropriate `layout.tsc`exportconstruntime='edge';// middleware.tsimporttype{NextRequest}from'next/server';import{NextResponse}from'next/server';exportfunctionmiddleware(request:NextRequest){consturl=request.url?.includes('_next') ?null :request.url;constsp=url?.split?.('?')?.[1]||'';constresponse=NextResponse.next();if(url!==null){response.headers.set('searchParams',sp);}returnresponse;}// Target layout componentimport{headers}from'next/headers';import{decodeState}from'state-in-url/encodeState';exportdefaultasyncfunctionLayout({  children,}:{children:React.ReactNode;}){constsp=headers().get('searchParams')||'';return(<div><Comp1searchParams={decodeState(sp,stateShape)}/>{children}</div>);}
With arbitrary state shape (not recommended)
Example
'use client'import{useUrlState}from'state-in-url/next';constsomeObj={};functionSettingsComponent(){const{ urlState, setUrl, setState}=useUrlState<object>(someObj);}

useUrlState hook for React-Router

API is same as for Next.js version, except can pass options fromNavigateOptions type.

API Docs

Example

exportconstform:Form={name:'',age:undefined,agree_to_terms:false,tags:[],};typeForm={name:string;age?:number;agree_to_terms:boolean;tags:{id:string;value:{text:string;time:Date}}[];};
import{useUrlState}from'state-in-url/react-router';import{form}from'./form';functionTagsComponent(){const{ urlState, setUrl, setState}=useUrlState(form);constonChangeTags=React.useCallback((tag:(typeoftags)[number])=>{setUrl((curr)=>({        ...curr,tags:curr.tags.find((t)=>t.id===tag.id)          ?curr.tags.filter((t)=>t.id!==tag.id)          :curr.tags.concat(tag),}));},[setUrl],);return(<div><Fieldtext="Tags"><divclassName="flex flex-wrap gap-2">{tags.map((tag)=>(<Tagactive={!!urlState.tags.find((t)=>t.id===tag.id)}text={tag.value.text}onClick={()=>onChangeTags(tag)}key={tag.id}/>))}</div></Field><inputvalue={urlState.name}onChange={(ev)=>{setState(curr=>({ ...curr,name:ev.target.value}))}}// Can update state immediately but sync change to url as neededonBlur={()=>setUrl()}/></div>);}consttags=[{id:'1',value:{text:'React.js',time:newDate('2024-07-17T04:53:17.000Z')},},{id:'2',value:{text:'Next.js',time:newDate('2024-07-18T04:53:17.000Z')},},{id:'3',value:{text:'TailwindCSS',time:newDate('2024-07-19T04:53:17.000Z')},},];

Example code

Other hooks and helpers

useUrlStateBase hook for others routers

Hooks to create your ownuseUrlState hooks with other routers, e.g. react-router or tanstack router.

API Docs

useSharedState hook for React.js

Hook to share state between any React components, tested with Next.js and Vite.

'use client'import{useSharedState}from'state-in-url';exportconstsomeState={name:''};functionSettingsComponent(){const{ state, setState}=useSharedState(someState);}

API Docs

useUrlEncode hook for React.js

API Docs

encodeState anddecodeState helpers

API Docs

encode anddecode helpers

API Docs

Best Practices

  • Define your state shape as a constant
  • Use TypeScript for enhanced type safety and autocomplete
  • Avoid storing sensitive information in URL parameters (SSN, API keys etc)
  • Use thisextension for readable TS errors

Can create state hooks for slices of state, and reuse them across application. For example:

typeUserState={name:string;age:number;other:{id:string,value:number}[]};constuserState={name:'',age:0,other:[],};exportconstuseUserState=()=>{const{ urlState, setUrl, reset}=useUrlState(userState);// other logic// reset query params when navigating to other pageReact.useEffect(()=>{returnreset},[])return{userState:urlState,setUserState:setUrl};;}

Gotchas

  1. Can pass only serializable values,Function,BigInt orSymbol won't work, probably things likeArrayBuffer neither. Everything that can be serialized to JSON will work.
  2. Vercel servers limit size of headers (query string and other stuff) to14KB, so keep your URL state under ~5000 words.https://vercel.com/docs/errors/URL_TOO_LONG
  3. Tested withnext.js 14/15 with app router, no plans to support pages.

Other

Contribute and/or run locally

SeeContributing doc

Roadmap

  • hook forNext.js
  • hook forreact-router
  • hook forremix
  • hook forsvelte
  • hook forastro
  • hook for store state in hash ?

Contact & Support

  • Create aGitHub issue for bug reports, feature requests, or questions

License

This project is licensed under theMIT license.

Inspiration

NUQS

Using URL to store state in Vue

Storing state in the URL

NextJS useSearchParams

About

Store any user state in query parameters; imagine JSON in a browser URL, while keeping types and structure of data, e.g.numbers will be decoded as numbers not strings. With TS validation. Shared state and URL state sync without any hassle or boilerplate. Supports Next.js@14-15, and react-router@6-7.

Topics

Resources

License

Code of conduct

Security policy

Stars

Watchers

Forks

Sponsor this project


    [8]ページ先頭

    ©2009-2025 Movatter.jp