|
1 |
| -import { useRouter, useRouterState } from '@tanstack/react-router' |
| 1 | +import { useLocation, useNavigate } from '@tanstack/react-router' |
2 | 2 | import { startTransition, useCallback, useMemo } from 'react'
|
| 3 | +import { renderQueryString } from '../url-encoding' |
3 | 4 | import { createAdapterProvider } from './lib/context'
|
4 | 5 | import type { AdapterInterface, UpdateUrlFunction } from './lib/defs'
|
5 | 6 |
|
6 | 7 | function useNuqsTanstackRouterAdapter(): AdapterInterface {
|
7 |
| - const state = useRouterState() |
8 |
| - const router = useRouter() |
9 |
| - |
10 |
| - const search = useMemo( |
11 |
| - () => new URLSearchParams(state.location.search || ''), |
12 |
| - [state.location.search] |
| 8 | + const search = useLocation({ select: state => state.search }) |
| 9 | + const navigate = useNavigate() |
| 10 | + const searchParams = useMemo( |
| 11 | + () => |
| 12 | + // search is a Record<string, string | string[]>, |
| 13 | + // so we need to flatten it into a list of key/value pairs, |
| 14 | + // replicating keys that have multiple values before passing it |
| 15 | + // to URLSearchParams, otherwise { foo: ['bar', 'baz'] } |
| 16 | + // ends up as { foo → 'bar,baz' } instead of { foo → 'bar', foo → 'baz' } |
| 17 | + new URLSearchParams( |
| 18 | + Object.entries(search).flatMap(([key, value]) => { |
| 19 | + if (Array.isArray(value)) { |
| 20 | + return value.map(v => [key, v]) |
| 21 | + } else { |
| 22 | + return [[key, value]] |
| 23 | + } |
| 24 | + }) |
| 25 | + ), |
| 26 | + [search] |
13 | 27 | )
|
14 | 28 |
|
15 | 29 | const updateUrl: UpdateUrlFunction = useCallback(
|
16 | 30 | (search, options) => {
|
| 31 | + // Wrapping in a startTransition seems to be necessary |
| 32 | + // to support scroll restoration |
17 | 33 | startTransition(() => {
|
18 |
| - const url = renderURL(location.pathname, search) |
19 |
| - router.navigate({ |
20 |
| - from: state.location.pathname, |
21 |
| - to: url, |
22 |
| - search: search, |
| 34 | + navigate({ |
| 35 | + // I know the docs say to use `search` here, but it would require |
| 36 | + // userland code to stitch the nuqs definitions to the route declarations |
| 37 | + // in order for TSR to serialize them, which kind of breaks the |
| 38 | + // "works out of the box" promise, and it also wouldn't support |
| 39 | + // the custom URL encoding. |
| 40 | + // TBC if it causes issues with consuming those search params |
| 41 | + // in other parts of the app. |
| 42 | + to: renderQueryString(search), |
23 | 43 | replace: options.history === 'replace',
|
24 |
| - resetScroll: options.scroll |
| 44 | + resetScroll: options.scroll, |
| 45 | + hash: prevHash => prevHash ?? '' |
25 | 46 | })
|
26 | 47 | })
|
27 | 48 | },
|
28 |
| - [router.navigate] |
| 49 | + [navigate] |
29 | 50 | )
|
30 | 51 |
|
31 | 52 | return {
|
32 |
| - searchParams: search, |
| 53 | + searchParams, |
33 | 54 | updateUrl,
|
34 | 55 | rateLimitFactor: 1
|
35 | 56 | }
|
36 | 57 | }
|
37 | 58 |
|
38 |
| -function renderURL(pathname: string, search: URLSearchParams) { |
39 |
| - const hashlessBase = pathname.split('#')[0] ?? '' |
40 |
| - const query = search.toString() ? `?${search.toString()}` : '' |
41 |
| - const hash = location.hash |
42 |
| - return hashlessBase + query + hash |
43 |
| -} |
44 |
| - |
45 | 59 | export const NuqsAdapter = createAdapterProvider(useNuqsTanstackRouterAdapter)
|
0 commit comments