Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore: Test a theory for RR sync #830

Closed
wants to merge 6 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 9 additions & 21 deletions packages/docs/content/docs/options.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -102,27 +102,15 @@ function Component() {
This concept of _"shallow routing"_ is done via updates to the browser's
[History API](https://developer.mozilla.org/en-US/docs/Web/API/History_API/Working_with_the_History_API).

While the `useOptimisticSearchParams` and the adapter itself can handle shallow URL
updates triggered from state updater functions, for them to react to URL changes
triggered by explicit calls to the History API (either by first or third party code),
you'd have to enable sync:

```tsx
// Export available in:
// 'nuqs/adapters/remix'
// 'nuqs/adapters/react-router/v6'
// 'nuqs/adapters/react-router/v7'
// 'nuqs/adapters/react'
import { enableHistorySync } from 'nuqs/adapters/remix'

// Somewhere top-level (like app/root.tsx)
enableHistorySync()
```

Note that you may not need this if only using your framework's router.

It is opt-in as it patches the History APIs, which can have side effects
if third party code does it too.
<Callout title="Why not using shouldRevalidate?">
[`shouldRevalidate`](https://reactrouter.com/start/framework/route-module#shouldrevalidate)
is the idomatic way of opting out of running loaders on navigation, but nuqs uses
the opposite approach: opting in to running loaders only when needed.

In order to avoid specifying `shouldRevalidate` for every route, nuqs chose to
patch the history methods to enable shallow routing by default (on its own updates)
in React Router based frameworks.
</Callout>

## Scroll

Expand Down
4 changes: 1 addition & 3 deletions packages/e2e/react-router/v6/src/react-router.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { enableHistorySync, NuqsAdapter } from 'nuqs/adapters/react-router/v6'
import { NuqsAdapter } from 'nuqs/adapters/react-router/v6'
import {
createBrowserRouter,
createRoutesFromElements,
Expand All @@ -7,8 +7,6 @@ import {
} from 'react-router-dom'
import RootLayout from './layout'

enableHistorySync()

// Adapt the RRv7 / Remix default export for component into a Component export for v6
function load(mod: Promise<{ default: any; [otherExports: string]: any }>) {
return () =>
Expand Down
4 changes: 1 addition & 3 deletions packages/e2e/react-router/v7/app/root.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { enableHistorySync, NuqsAdapter } from 'nuqs/adapters/react-router/v7'
import { NuqsAdapter } from 'nuqs/adapters/react-router/v7'
import {
isRouteErrorResponse,
Links,
Expand All @@ -8,8 +8,6 @@ import {
ScrollRestoration
} from 'react-router'

enableHistorySync()

import type { Route } from './+types/root'

export function Layout({ children }: { children: React.ReactNode }) {
Expand Down
4 changes: 1 addition & 3 deletions packages/e2e/remix/app/root.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
import { Links, Meta, Scripts, ScrollRestoration } from '@remix-run/react'
import { enableHistorySync, NuqsAdapter } from 'nuqs/adapters/remix'
import { NuqsAdapter } from 'nuqs/adapters/remix'
import RootLayout from './layout'

enableHistorySync()

export function Layout({ children }: { children: React.ReactNode }) {
return (
<html lang="en">
Expand Down
29 changes: 29 additions & 0 deletions packages/e2e/remix/app/routes/sync.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { parseAsInteger, useQueryState } from 'nuqs'

function Test({ id }: { id: number }) {
console.log(`render test ${id}`)
const [state, setState] = useQueryState(
'test',
parseAsInteger.withDefault(0).withOptions({ shallow: false })
)
return <button onClick={() => setState(c => c + 1)}>{state}</button>
}

const delay = (ms: number) => new Promise(resolve => setTimeout(resolve, ms))

export async function loader() {
await delay(500)
return null
}

export default function Page() {
console.log('page render')
return (
<>
<Test id={1} />
<Test id={2} />
<Test id={3} />
<Test id={4} />
</>
)
}
12 changes: 12 additions & 0 deletions packages/nuqs/src/adapters/lib/patch-history.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,12 +51,24 @@ export function patchHistory(
if (history.nuqs?.adapters?.includes(adapter)) {
return
}
let lastSearchSeen = typeof location === 'object' ? location.search : ''

emitter.on('update', search => {
lastSearchSeen = search.toString()
})

debug(
'[nuqs %s] Patching history (%s adapter)',
'0.0.0-inject-version-here',
adapter
)
function sync(url: URL | string) {
try {
const newSearch = new URL(url, location.origin).search
if (newSearch === lastSearchSeen) {
return
}
} catch {}
try {
emitter.emit('update', getSearchParams(url))
} catch (e) {
Expand Down
3 changes: 0 additions & 3 deletions packages/nuqs/src/adapters/lib/react-router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,9 +92,6 @@ export function createReactRouterBasedAdapter(
window.removeEventListener('popstate', onPopState)
}
}, [])
useEffect(() => {
emitter.emit('update', serverSearchParams)
}, [serverSearchParams])
return searchParams
}
/**
Expand Down
10 changes: 0 additions & 10 deletions packages/nuqs/src/adapters/react-router.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,4 @@
export {
/**
* @deprecated This import will be removed in [email protected].
*
* Please pin your version of React Router in the import:
* - `nuqs/adapters/react-router/v6`
* - `nuqs/adapters/react-router/v7`.
*
* Note: this deprecated import (`nuqs/adapters/react-router`) is for React Router v6 only.
*/
enableHistorySync,
/**
* @deprecated This import will be removed in [email protected].
*
Expand Down
4 changes: 3 additions & 1 deletion packages/nuqs/src/adapters/react-router/v6.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ const {
useSearchParams
)

export { enableHistorySync, useOptimisticSearchParams }
export { useOptimisticSearchParams }

export const NuqsAdapter = createAdapterProvider(useNuqsReactRouterV6Adapter)

enableHistorySync()
4 changes: 3 additions & 1 deletion packages/nuqs/src/adapters/react-router/v7.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ const {
useSearchParams
)

export { enableHistorySync, useOptimisticSearchParams }
export { useOptimisticSearchParams }

export const NuqsAdapter = createAdapterProvider(useNuqsReactRouterV7Adapter)

enableHistorySync()
4 changes: 3 additions & 1 deletion packages/nuqs/src/adapters/remix.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ const {
useOptimisticSearchParams
} = createReactRouterBasedAdapter('remix', useNavigate, useSearchParams)

export { enableHistorySync, useOptimisticSearchParams }
export { useOptimisticSearchParams }

export const NuqsAdapter = createAdapterProvider(useNuqsRemixAdapter)

enableHistorySync()
14 changes: 4 additions & 10 deletions packages/nuqs/src/useQueryState.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,4 @@
import {
useCallback,
useEffect,
useInsertionEffect,
useRef,
useState
} from 'react'
import { useCallback, useEffect, useRef, useState } from 'react'
import { useAdapter } from './adapters/lib/context'
import { debug } from './debug'
import type { Options } from './defs'
Expand Down Expand Up @@ -260,7 +254,7 @@ export function useQueryState<T = string>(
}, [initialSearchParams?.get(key), key])

// Sync all hooks together & with external URL changes
useInsertionEffect(() => {
useEffect(() => {
function updateInternalState({ state, query }: CrossHookSyncPayload) {
debug('[nuqs `%s`] updateInternalState %O', key, state)
stateRef.current = state
Expand Down Expand Up @@ -288,7 +282,7 @@ export function useQueryState<T = string>(
) {
newValue = null
}
queryRef.current = enqueueQueryStringUpdate(key, newValue, serialize, {
const query = enqueueQueryStringUpdate(key, newValue, serialize, {
// Call-level options take precedence over hook declaration options.
history: options.history ?? history,
shallow: options.shallow ?? shallow,
Expand All @@ -297,7 +291,7 @@ export function useQueryState<T = string>(
startTransition: options.startTransition ?? startTransition
})
// Sync all hooks state (including this one)
emitter.emit(key, { state: newValue, query: queryRef.current })
emitter.emit(key, { state: newValue, query })
return scheduleFlushToURL(adapter)
},
[key, history, shallow, scroll, throttleMs, startTransition, adapter]
Expand Down
19 changes: 4 additions & 15 deletions packages/nuqs/src/useQueryStates.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,4 @@
import {
useCallback,
useEffect,
useInsertionEffect,
useMemo,
useRef,
useState
} from 'react'
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
import { useAdapter } from './adapters/lib/context'
import { debug } from './debug'
import type { Nullable, Options, UrlKeys } from './defs'
Expand Down Expand Up @@ -139,7 +132,7 @@ export function useQueryStates<KeyMap extends UseQueryStatesKeysMap>(
])

// Sync all hooks together & with external URL changes
useInsertionEffect(() => {
useEffect(() => {
function updateInternalState(state: V) {
debug('[nuq+ `%s`] updateInternalState %O', stateKeys, state)
stateRef.current = state
Expand Down Expand Up @@ -216,8 +209,7 @@ export function useQueryStates<KeyMap extends UseQueryStatesKeysMap>(
) {
value = null
}

queryRef.current[urlKey] = enqueueQueryStringUpdate(
const query = enqueueQueryStringUpdate(
urlKey,
value,
parser.serialize ?? String,
Expand All @@ -235,10 +227,7 @@ export function useQueryStates<KeyMap extends UseQueryStatesKeysMap>(
startTransition
}
)
emitter.emit(urlKey, {
state: value,
query: queryRef.current[urlKey] ?? null
})
emitter.emit(urlKey, { state: value, query })
}
return scheduleFlushToURL(adapter)
},
Expand Down
Loading