The React Router adapter enables nuqs to work with React Router applications. There are separate adapters for React Router v6 and v7.
Version Selection
React Router v6
For applications using react-router-dom@^6:
import { NuqsAdapter } from 'nuqs/adapters/react-router/v6'
React Router v7
For applications using react-router@^7:
import { NuqsAdapter } from 'nuqs/adapters/react-router/v7'
The generic import nuqs/adapters/react-router is deprecated and will be removed in nuqs v3.0.0. Always specify the version explicitly.
Installation
Install nuqs
Install nuqs in your React Router project: Set up the adapter for your version
Choose the setup instructions for your React Router version: React Router v6
React Router v7
Wrap your RouterProvider with the NuqsAdapter:import { NuqsAdapter } from 'nuqs/adapters/react-router/v6'
import { createBrowserRouter, RouterProvider } from 'react-router-dom'
import { createRoot } from 'react-dom/client'
import App from './App'
const router = createBrowserRouter([
{
path: '/',
element: <App />,
children: [
// Your routes here
]
}
])
createRoot(document.getElementById('root')!).render(
<NuqsAdapter>
<RouterProvider router={router} />
</NuqsAdapter>
)
Wrap your app with the NuqsAdapter in your root component:import { NuqsAdapter } from 'nuqs/adapters/react-router/v7'
import { Outlet } from 'react-router'
export default function Root() {
return (
<NuqsAdapter>
<div>
<nav>{/* Navigation */}</nav>
<Outlet />
</div>
</NuqsAdapter>
)
}
Use nuqs hooks in your routes
Now you can use useQueryState and useQueryStates in any route component:import { useQueryState } from 'nuqs'
export default function SearchRoute() {
const [search, setSearch] = useQueryState('q')
return (
<div>
<input
value={search || ''}
onChange={(e) => setSearch(e.target.value)}
placeholder="Search..."
/>
<p>Search query: {search || 'none'}</p>
</div>
)
}
Version Requirements
React Router v6
- react-router-dom:
^6
- React:
>=18.2.0 or ^19.0.0-0
React Router v7
- react-router:
^7
- React:
>=18.2.0 or ^19.0.0-0
Features
Integration with React Router Navigation
Both adapters use React Router’s native useNavigate() and useSearchParams() hooks, ensuring perfect integration with React Router’s navigation system.
Optimistic UI Updates
Use useOptimisticSearchParams for optimistic UI updates during navigation:
import { useOptimisticSearchParams } from 'nuqs/adapters/react-router/v6'
// or
import { useOptimisticSearchParams } from 'nuqs/adapters/react-router/v7'
export function SearchResults() {
const searchParams = useOptimisticSearchParams()
const query = searchParams.get('q')
return <div>Query: {query}</div>
}
This returns the search params that will be applied after pending transitions complete.
Shallow Updates
By default, URL updates are shallow (client-side only):
const [state, setState] = useQueryState('key')
// Client-side only update
To trigger full page navigation, use shallow: false:
const [state, setState] = useQueryState('key', { shallow: false })
Dynamic Routes
The adapter works seamlessly with dynamic route segments:
import { useParams } from 'react-router-dom'
import { useQueryState, parseAsInteger } from 'nuqs'
export default function PostRoute() {
const { id } = useParams()
const [page, setPage] = useQueryState('page', parseAsInteger.withDefault(1))
return (
<div>
<h1>Post {id}</h1>
<p>Page {page}</p>
<button onClick={() => setPage(page + 1)}>Next Page</button>
</div>
)
}
How It Works
Both React Router adapters:
- Use React Router’s
useSearchParams() to read current search params
- Use React Router’s
useNavigate() to update the URL
- Automatically handle route transitions and history changes
- Provide optimistic updates through
useOptimisticSearchParams()
- Filter and sync only the relevant search params for each component
Examples
Tabbed Interface
import { useQueryState, parseAsStringLiteral } from 'nuqs'
const tabs = ['overview', 'settings', 'advanced'] as const
export function TabbedInterface() {
const [activeTab, setActiveTab] = useQueryState(
'tab',
parseAsStringLiteral(tabs).withDefault('overview')
)
return (
<div>
<div>
{tabs.map((tab) => (
<button
key={tab}
onClick={() => setActiveTab(tab)}
className={activeTab === tab ? 'active' : ''}
>
{tab}
</button>
))}
</div>
<div>
{activeTab === 'overview' && <OverviewTab />}
{activeTab === 'settings' && <SettingsTab />}
{activeTab === 'advanced' && <AdvancedTab />}
</div>
</div>
)
}
Product Filters with Multiple States
import { useQueryStates, parseAsString, parseAsInteger, parseAsBoolean } from 'nuqs'
export function ProductFilters() {
const [filters, setFilters] = useQueryStates({
search: parseAsString,
category: parseAsString,
minPrice: parseAsInteger,
maxPrice: parseAsInteger,
inStock: parseAsBoolean
})
return (
<div>
<input
value={filters.search || ''}
onChange={(e) => setFilters({ search: e.target.value || null })}
placeholder="Search products..."
/>
<select
value={filters.category || ''}
onChange={(e) => setFilters({ category: e.target.value || null })}
>
<option value="">All Categories</option>
<option value="electronics">Electronics</option>
<option value="clothing">Clothing</option>
</select>
<label>
<input
type="checkbox"
checked={filters.inStock || false}
onChange={(e) => setFilters({ inStock: e.target.checked || null })}
/>
In Stock Only
</label>
<button onClick={() => setFilters({
search: null,
category: null,
minPrice: null,
maxPrice: null,
inStock: null
})}>
Clear Filters
</button>
</div>
)
}
import { useQueryState, parseAsInteger } from 'nuqs'
export function PaginatedList() {
const [page, setPage] = useQueryState(
'page',
parseAsInteger.withDefault(1).withOptions({
history: 'push' // Create history entries for pagination
})
)
return (
<div>
<div>{/* Render items for current page */}</div>
<div>
<button
onClick={() => setPage(page - 1)}
disabled={page <= 1}
>
Previous
</button>
<span>Page {page}</span>
<button onClick={() => setPage(page + 1)}>
Next
</button>
</div>
</div>
)
}
Troubleshooting
Wrong adapter version
Make sure you’re using the correct adapter for your React Router version:
// React Router v6 ✅
import { NuqsAdapter } from 'nuqs/adapters/react-router/v6'
import { /* ... */ } from 'react-router-dom'
// React Router v7 ✅
import { NuqsAdapter } from 'nuqs/adapters/react-router/v7'
import { /* ... */ } from 'react-router'
Search params not updating
Ensure:
- The
NuqsAdapter wraps your router or app
- Components using nuqs hooks are inside the router context
- You’re not preventing default on navigation events
Deprecation warning
If you see a deprecation warning about nuqs/adapters/react-router, update your import to specify the version:
// ❌ Deprecated (will be removed in v3.0.0)
import { NuqsAdapter } from 'nuqs/adapters/react-router'
// ✅ Correct
import { NuqsAdapter } from 'nuqs/adapters/react-router/v6'
// or
import { NuqsAdapter } from 'nuqs/adapters/react-router/v7'