diff --git a/packages/docs/content/docs/adapters.mdx b/packages/docs/content/docs/adapters.mdx
index 6ec4adf0..0dd0b04a 100644
--- a/packages/docs/content/docs/adapters.mdx
+++ b/packages/docs/content/docs/adapters.mdx
@@ -127,6 +127,14 @@ export function ReactRouter() {
}
```
+
+
+ Only `BrowserRouter` is supported. There may be support for `HashRouter`
+ in the future (see issue [#810](https://github.com/47ng/nuqs/issues/810)), but
+ support for `MemoryRouter` is not planned.
+
+
+
## React Router v7
```tsx title="app/root.tsx"
@@ -151,6 +159,9 @@ export default function App() {
Please pin your imports to the specific version,
eg: `nuqs/adapters/react-router/v6` or `nuqs/adapters/react-router/v7`.
+
+ The main difference is where the React Router hooks are imported from:
+ `react-router-dom` for v6, and `react-router` for v7.
## Testing
diff --git a/packages/e2e/react-router/v6/cypress/e2e/repro-839.cy.ts b/packages/e2e/react-router/v6/cypress/e2e/repro-839.cy.ts
new file mode 100644
index 00000000..ea5a1912
--- /dev/null
+++ b/packages/e2e/react-router/v6/cypress/e2e/repro-839.cy.ts
@@ -0,0 +1,5 @@
+import { testRepro839LocationStatePersistence } from 'e2e-shared/specs/react-router/repro-839-location-state-persistence.cy'
+
+testRepro839LocationStatePersistence({
+ path: '/repro-839'
+})
diff --git a/packages/e2e/react-router/v6/src/react-router.tsx b/packages/e2e/react-router/v6/src/react-router.tsx
index 8dfb65b6..95a2647a 100644
--- a/packages/e2e/react-router/v6/src/react-router.tsx
+++ b/packages/e2e/react-router/v6/src/react-router.tsx
@@ -20,6 +20,7 @@ function load(mod: Promise<{ default: any; [otherExports: string]: any }>) {
const router = createBrowserRouter(
createRoutesFromElements(
} >,
+ {/* Shared E2E tests */}
@@ -40,6 +41,8 @@ const router = createBrowserRouter(
+ {/* Reproductions */}
+
))
diff --git a/packages/e2e/react-router/v6/src/routes/repro-839.tsx b/packages/e2e/react-router/v6/src/routes/repro-839.tsx
new file mode 100644
index 00000000..8b608aa0
--- /dev/null
+++ b/packages/e2e/react-router/v6/src/routes/repro-839.tsx
@@ -0,0 +1,6 @@
+import { Repro839 } from 'e2e-shared/specs/react-router/repro-839-location-state-persistence'
+import { useLocation, useNavigate } from 'react-router-dom'
+
+export default function Page() {
+ return
+}
diff --git a/packages/e2e/react-router/v7/app/routes.ts b/packages/e2e/react-router/v7/app/routes.ts
index 6cebf5a3..03299a3a 100644
--- a/packages/e2e/react-router/v7/app/routes.ts
+++ b/packages/e2e/react-router/v7/app/routes.ts
@@ -3,6 +3,7 @@ import { type RouteConfig, layout, route } from '@react-router/dev/routes'
export default [
// prettier-ignore
layout('layout.tsx', [
+ // Shared E2E tests
route('/hash-preservation', './routes/hash-preservation.tsx'),
route('/basic-io/useQueryState', './routes/basic-io.useQueryState.tsx'),
route('/basic-io/useQueryStates', './routes/basic-io.useQueryStates.tsx'),
@@ -22,6 +23,8 @@ export default [
route('/form/useQueryState', './routes/form.useQueryState.tsx'),
route('/form/useQueryStates', './routes/form.useQueryStates.tsx'),
route('/referential-stability/useQueryState', './routes/referential-stability.useQueryState.tsx'),
- route('/referential-stability/useQueryStates', './routes/referential-stability.useQueryStates.tsx')
+ route('/referential-stability/useQueryStates', './routes/referential-stability.useQueryStates.tsx'),
+ // Reproductions
+ route('/repro-839', './routes/repro-839.tsx'),
])
] satisfies RouteConfig
diff --git a/packages/e2e/react-router/v7/app/routes/repro-839.tsx b/packages/e2e/react-router/v7/app/routes/repro-839.tsx
new file mode 100644
index 00000000..33f6314e
--- /dev/null
+++ b/packages/e2e/react-router/v7/app/routes/repro-839.tsx
@@ -0,0 +1,6 @@
+import { Repro839 } from 'e2e-shared/specs/react-router/repro-839-location-state-persistence'
+import { useLocation, useNavigate } from 'react-router'
+
+export default function Page() {
+ return
+}
diff --git a/packages/e2e/react-router/v7/cypress/e2e/repro-839.cy.ts b/packages/e2e/react-router/v7/cypress/e2e/repro-839.cy.ts
new file mode 100644
index 00000000..ea5a1912
--- /dev/null
+++ b/packages/e2e/react-router/v7/cypress/e2e/repro-839.cy.ts
@@ -0,0 +1,5 @@
+import { testRepro839LocationStatePersistence } from 'e2e-shared/specs/react-router/repro-839-location-state-persistence.cy'
+
+testRepro839LocationStatePersistence({
+ path: '/repro-839'
+})
diff --git a/packages/e2e/remix/app/routes/repro-839.tsx b/packages/e2e/remix/app/routes/repro-839.tsx
new file mode 100644
index 00000000..541dc5da
--- /dev/null
+++ b/packages/e2e/remix/app/routes/repro-839.tsx
@@ -0,0 +1,6 @@
+import { useLocation, useNavigate } from '@remix-run/react'
+import { Repro839 } from 'e2e-shared/specs/react-router/repro-839-location-state-persistence'
+
+export default function Page() {
+ return
+}
diff --git a/packages/e2e/remix/cypress/e2e/repro-839.cy.ts b/packages/e2e/remix/cypress/e2e/repro-839.cy.ts
new file mode 100644
index 00000000..ea5a1912
--- /dev/null
+++ b/packages/e2e/remix/cypress/e2e/repro-839.cy.ts
@@ -0,0 +1,5 @@
+import { testRepro839LocationStatePersistence } from 'e2e-shared/specs/react-router/repro-839-location-state-persistence.cy'
+
+testRepro839LocationStatePersistence({
+ path: '/repro-839'
+})
diff --git a/packages/e2e/shared/specs/react-router/repro-839-location-state-persistence.cy.ts b/packages/e2e/shared/specs/react-router/repro-839-location-state-persistence.cy.ts
new file mode 100644
index 00000000..5b3a36d3
--- /dev/null
+++ b/packages/e2e/shared/specs/react-router/repro-839-location-state-persistence.cy.ts
@@ -0,0 +1,22 @@
+import { createTest } from '../../create-test'
+
+export const testRepro839LocationStatePersistence = createTest(
+ 'Repro for issue #839 - Location state persistence',
+ ({ path }) => {
+ it('persists location.state on shallow URL updates', () => {
+ cy.visit(path)
+ cy.contains('#hydration-marker', 'hydrated').should('be.hidden')
+ cy.get('#setup').click()
+ cy.get('#shallow').click()
+ cy.get('#state').should('have.text', '{"test":"pass"}')
+ })
+
+ it('persists location.state on deep URL updates', () => {
+ cy.visit(path)
+ cy.contains('#hydration-marker', 'hydrated').should('be.hidden')
+ cy.get('#setup').click()
+ cy.get('#deep').click()
+ cy.get('#state').should('have.text', '{"test":"pass"}')
+ })
+ }
+)
diff --git a/packages/e2e/shared/specs/react-router/repro-839-location-state-persistence.tsx b/packages/e2e/shared/specs/react-router/repro-839-location-state-persistence.tsx
new file mode 100644
index 00000000..0ecd6026
--- /dev/null
+++ b/packages/e2e/shared/specs/react-router/repro-839-location-state-persistence.tsx
@@ -0,0 +1,34 @@
+import { useQueryState } from 'nuqs'
+
+type Repro839Props = {
+ useNavigate: () => (url: string, options: { state: unknown }) => void
+ useLocation: () => { state: unknown }
+}
+
+export function Repro839({ useNavigate, useLocation }: Repro839Props) {
+ const navigate = useNavigate()
+ const location = useLocation()
+ const [, setShallow] = useQueryState('shallow', {
+ shallow: true
+ })
+ const [, setDeep] = useQueryState('deep', {
+ shallow: false
+ })
+ return (
+ <>
+
+
+
+
{JSON.stringify(location.state)}
+ >
+ )
+}
diff --git a/packages/nuqs/src/adapters/lib/react-router.ts b/packages/nuqs/src/adapters/lib/react-router.ts
index 846d6758..408b2df6 100644
--- a/packages/nuqs/src/adapters/lib/react-router.ts
+++ b/packages/nuqs/src/adapters/lib/react-router.ts
@@ -16,6 +16,7 @@ type NavigateUrl = {
type NavigateOptions = {
replace?: boolean
preventScrollReset?: boolean
+ state?: unknown
}
type NavigateFn = (url: NavigateUrl, options: NavigateOptions) => void
type UseNavigate = () => NavigateFn
@@ -60,7 +61,8 @@ export function createReactRouterBasedAdapter(
},
{
replace: true,
- preventScrollReset: true
+ preventScrollReset: true,
+ state: history.state?.usr
}
)
}