1
1
import {
2
2
stringifySearchWith ,
3
+ parseSearchWith ,
3
4
useLocation ,
4
5
useMatches ,
5
- useNavigate
6
+ useNavigate ,
7
+ type AnySchema
6
8
} from '@tanstack/react-router'
7
- import { startTransition , useCallback , useMemo } from 'react'
9
+ import {
10
+ createContext ,
11
+ createElement ,
12
+ startTransition ,
13
+ useCallback ,
14
+ useContext ,
15
+ useMemo ,
16
+ type ReactElement ,
17
+ type ReactNode
18
+ } from 'react'
8
19
import { renderQueryString } from '../lib/url-encoding'
9
- import { createAdapterProvider , type AdapterProvider } from './lib/context'
20
+ import { createAdapterProvider , type AdapterProps } from './lib/context'
10
21
import type { AdapterInterface , UpdateUrlFunction } from './lib/defs'
11
22
12
23
// Use TanStack Router's default JSON-based search param serialization
24
+ // The default behavior is compatible with nuqs' expected behavior
13
25
const defaultStringifySearch = stringifySearchWith ( JSON . stringify )
26
+ const defaultParseSearch = parseSearchWith ( JSON . parse )
27
+
28
+ type TanstackRouterAdapterContextType = {
29
+ stringifySearchWith ?: ( search : Record < string , any > ) => string
30
+ }
31
+
32
+ const NuqsTanstackRouterAdapterContext =
33
+ createContext < TanstackRouterAdapterContextType > ( {
34
+ stringifySearchWith : undefined
35
+ } )
14
36
15
37
function useNuqsTanstackRouterAdapter ( watchKeys : string [ ] ) : AdapterInterface {
38
+ const { stringifySearchWith } = useContext ( NuqsTanstackRouterAdapterContext )
39
+
16
40
const search = useLocation ( {
17
41
select : state =>
18
42
Object . fromEntries (
@@ -26,17 +50,33 @@ function useNuqsTanstackRouterAdapter(watchKeys: string[]): AdapterInterface {
26
50
? ( matches [ matches . length - 1 ] ?. fullPath as string )
27
51
: undefined
28
52
} )
29
- const searchParams = useMemo (
30
- ( ) =>
31
- // Use TSR’s default stringify to convert search object → URLSearchParams.
32
- // This avoids issues where arrays/objects were previously flattened
33
- // into invalid values like "[object Object]".
34
- new URLSearchParams ( defaultStringifySearch ( search ) ) ,
35
- [ search , watchKeys . join ( ',' ) ]
36
- )
53
+ const searchParams = useMemo ( ( ) => {
54
+ // Regardless of whether the user specified a custom parseSearchWith,
55
+ // the search object here is already the result after parsing.
56
+ // We use the default defaultStringifySearch to convert the search
57
+ // to search params that nuqs can handle correctly.
58
+ //
59
+ // Use TSR's default stringify to convert search object → URLSearchParams.
60
+ // This avoids issues where arrays/objects were previously flattened
61
+ // into invalid values like "[object Object]".
62
+ return new URLSearchParams ( defaultStringifySearch ( search ) )
63
+ } , [ search , watchKeys . join ( ',' ) ] )
37
64
38
65
const updateUrl : UpdateUrlFunction = useCallback (
39
66
( search , options ) => {
67
+ let processedSearch : URLSearchParams
68
+ if ( stringifySearchWith ) {
69
+ // When updating, the search (URLSearchParams) here is in nuqs-generated format.
70
+ // We first use defaultParseSearch to parse it into a search object,
71
+ // then use the custom stringifySearchWith to convert it to a new URLSearchParams.
72
+ const searchObject = defaultParseSearch ( search . toString ( ) )
73
+ const customQueryString = stringifySearchWith ( searchObject )
74
+ processedSearch = new URLSearchParams ( customQueryString )
75
+ } else {
76
+ // Use default behavior which is compatible with nuqs' expected behavior
77
+ processedSearch = search
78
+ }
79
+
40
80
// Wrapping in a startTransition seems to be necessary
41
81
// to support scroll restoration
42
82
startTransition ( ( ) => {
@@ -52,7 +92,7 @@ function useNuqsTanstackRouterAdapter(watchKeys: string[]): AdapterInterface {
52
92
// When we clear the search, passing an empty string causes
53
93
// a type error and possible basepath issues, so we switch it to '.' instead.
54
94
// See https://github.com/47ng/nuqs/pull/953#issuecomment-3003583471
55
- to : renderQueryString ( search ) || '.' ,
95
+ to : renderQueryString ( processedSearch ) || '.' ,
56
96
// `from` will be handled by tanstack router match resolver, code snippet:
57
97
// https://github.com/TanStack/router/blob/5d940e2d8bdb12e213eede0abe8012855433ec4b/packages/react-router/src/link.tsx#L108-L112
58
98
...( from ? { from } : { } ) ,
@@ -62,7 +102,7 @@ function useNuqsTanstackRouterAdapter(watchKeys: string[]): AdapterInterface {
62
102
} )
63
103
} )
64
104
} ,
65
- [ navigate , from ]
105
+ [ navigate , from , stringifySearchWith ]
66
106
)
67
107
68
108
return {
@@ -72,6 +112,21 @@ function useNuqsTanstackRouterAdapter(watchKeys: string[]): AdapterInterface {
72
112
}
73
113
}
74
114
75
- export const NuqsAdapter : AdapterProvider = createAdapterProvider (
115
+ const NuqsTanstackRouterAdapter = createAdapterProvider (
76
116
useNuqsTanstackRouterAdapter
77
117
)
118
+
119
+ export function NuqsAdapter ( {
120
+ children,
121
+ stringifySearchWith,
122
+ ...adapterProps
123
+ } : AdapterProps & {
124
+ children : ReactNode
125
+ stringifySearchWith ?: ( search : Record < string , any > ) => string
126
+ } ) : ReactElement {
127
+ return createElement (
128
+ NuqsTanstackRouterAdapterContext . Provider ,
129
+ { value : { stringifySearchWith } } ,
130
+ createElement ( NuqsTanstackRouterAdapter , { ...adapterProps , children } )
131
+ )
132
+ }
0 commit comments