nuqs icon indicating copy to clipboard operation
nuqs copied to clipboard

feat: Key isolation

Open franky47 opened this issue 1 year ago • 2 comments

Key isolation (also known as "fine grained subscriptions") is the fact that updating a search param only re-renders hooks subscribed to this key.

Example:

  • <ComponentA> has a useQueryState('a')
  • <ComponentB> has a useQueryState('b')

Updating the state for a doesn't re-render ComponentB.

Most frameworks (Next.js & React Router) re-render every call site of their useSearchParams hooks when any search params change (via navigation events), causing unnecessary re-renders of useQueryState(s). Some other routers like TanStack Router do implement key isolation.

Since in the React SPA adapter we control the whole thing (there is no router so to speak), we can implement caching to detect only relevant differences and avoid re-rendering unnecessarily.

For React Router & Remix, we can do this too in shallow mode, since their useSearchParams doesn't re-render on shallow updates. When using shallow: false, everything would re-render after the URL has updated (when coming back from the loader). This feels like a natural behaviour that is in line with the frameworks navigation.

For Next.js, this might require closer collaboration with the core team to implement this filtering built-in, because their useSearchParams does re-render on any search param change, even when done in a shallow manner. This is because it uses a single Context that is reactive to shallow updates (so we can't do the same trick as in the other routers):

  • Value: https://github.com/vercel/next.js/blob/90164a10017c7a7f42d26c7ac36c4cc1db258a6c/packages/next/src/client/components/app-router.tsx#L255-L268
  • Provider: https://github.com/vercel/next.js/blob/90164a10017c7a7f42d26c7ac36c4cc1db258a6c/packages/next/src/client/components/app-router.tsx#L645
  • Consumer: https://github.com/vercel/next.js/blob/90164a10017c7a7f42d26c7ac36c4cc1db258a6c/packages/next/src/client/components/navigation.ts#L42

franky47 avatar Jan 09 '25 19:01 franky47

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Preview Comments Updated (UTC)
nuqs Ready Ready Preview Comment Aug 18, 2025 11:36am

vercel[bot] avatar Jan 09 '25 19:01 vercel[bot]

pnpm add https://pkg.pr.new/nuqs@855

commit: 45869d2

pkg-pr-new[bot] avatar Jan 09 '25 19:01 pkg-pr-new[bot]

:tada: This PR is included in version 2.5.0-beta.6 :tada:

The release is available on:

Your semantic-release bot :package::rocket:

github-actions[bot] avatar Aug 18 '25 11:08 github-actions[bot]

:tada: This PR is included in version 2.5.0 :tada:

The release is available on:

Your semantic-release bot :package::rocket:

github-actions[bot] avatar Aug 22 '25 14:08 github-actions[bot]

@franky47 are there any solutions you can think of that we can implement currently that would prevent excessive re-renders? we're currently use deckgl to lift state from the url but its causing multiple rerenders which results in unexpected layer rendering

michaelbayday avatar Sep 10 '25 00:09 michaelbayday

The React Compiler is one that comes to mind, it would memoise components so they don't rerender if their props are the same. It may require you to slice your components a little differently, with a parent component doing the URL state access, and feeding that (or derived props) to heavy children components that'd be target for auto-memoisation.

You could also do the same thing the compiler does, with React.memo, but it's a lot more work and can be brittle, more here:

http://podrocket.logrocket.com/the-useless-usecallback-react-performance-myths-dominik-dorfmeister

franky47 avatar Sep 10 '25 03:09 franky47