11import {
22 stringifySearchWith ,
3+ parseSearchWith ,
34 useLocation ,
45 useMatches ,
5- useNavigate
6+ useNavigate ,
7+ type AnySchema
68} 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'
819import { renderQueryString } from '../lib/url-encoding'
9- import { createAdapterProvider , type AdapterProvider } from './lib/context'
20+ import { createAdapterProvider , type AdapterProps } from './lib/context'
1021import type { AdapterInterface , UpdateUrlFunction } from './lib/defs'
1122
1223// Use TanStack Router's default JSON-based search param serialization
24+ // The default behavior is compatible with nuqs' expected behavior
1325const 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+ } )
1436
1537function useNuqsTanstackRouterAdapter ( watchKeys : string [ ] ) : AdapterInterface {
38+ const { stringifySearchWith } = useContext ( NuqsTanstackRouterAdapterContext )
39+
1640 const search = useLocation ( {
1741 select : state =>
1842 Object . fromEntries (
@@ -26,17 +50,33 @@ function useNuqsTanstackRouterAdapter(watchKeys: string[]): AdapterInterface {
2650 ? ( matches [ matches . length - 1 ] ?. fullPath as string )
2751 : undefined
2852 } )
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 ( ',' ) ] )
3764
3865 const updateUrl : UpdateUrlFunction = useCallback (
3966 ( 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+
4080 // Wrapping in a startTransition seems to be necessary
4181 // to support scroll restoration
4282 startTransition ( ( ) => {
@@ -52,7 +92,7 @@ function useNuqsTanstackRouterAdapter(watchKeys: string[]): AdapterInterface {
5292 // When we clear the search, passing an empty string causes
5393 // a type error and possible basepath issues, so we switch it to '.' instead.
5494 // See https://github.com/47ng/nuqs/pull/953#issuecomment-3003583471
55- to : renderQueryString ( search ) || '.' ,
95+ to : renderQueryString ( processedSearch ) || '.' ,
5696 // `from` will be handled by tanstack router match resolver, code snippet:
5797 // https://github.com/TanStack/router/blob/5d940e2d8bdb12e213eede0abe8012855433ec4b/packages/react-router/src/link.tsx#L108-L112
5898 ...( from ? { from } : { } ) ,
@@ -62,7 +102,7 @@ function useNuqsTanstackRouterAdapter(watchKeys: string[]): AdapterInterface {
62102 } )
63103 } )
64104 } ,
65- [ navigate , from ]
105+ [ navigate , from , stringifySearchWith ]
66106 )
67107
68108 return {
@@ -72,6 +112,21 @@ function useNuqsTanstackRouterAdapter(watchKeys: string[]): AdapterInterface {
72112 }
73113}
74114
75- export const NuqsAdapter : AdapterProvider = createAdapterProvider (
115+ const NuqsTanstackRouterAdapter = createAdapterProvider (
76116 useNuqsTanstackRouterAdapter
77117)
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