@@ -3,7 +3,7 @@ import React from 'react'
3
3
import { debug } from './debug'
4
4
import type { Options } from './defs'
5
5
import type { Parser } from './parsers'
6
- import { SYNC_EVENT_KEY , emitter } from './sync'
6
+ import { SYNC_EVENT_KEY , emitter , type CrossHookSyncPayload } from './sync'
7
7
import {
8
8
FLUSH_RATE_LIMIT_MS ,
9
9
enqueueQueryStringUpdate ,
@@ -225,10 +225,12 @@ export function useQueryState<T = string>(
225
225
const router = useRouter ( )
226
226
// Not reactive, but available on the server and on page load
227
227
const initialSearchParams = useSearchParams ( )
228
+ const queryRef = React . useRef < string | null > ( null )
228
229
const [ internalState , setInternalState ] = React . useState < T | null > ( ( ) => {
229
230
const queueValue = getQueuedValue ( key )
230
231
const urlValue = initialSearchParams ?. get ( key ) ?? null
231
232
const value = queueValue ?? urlValue
233
+ queryRef . current = value
232
234
return value === null ? null : safeParse ( parse , value , key )
233
235
} )
234
236
const stateRef = React . useRef ( internalState )
@@ -245,25 +247,33 @@ export function useQueryState<T = string>(
245
247
if ( window . next ?. version !== '14.0.3' ) {
246
248
return
247
249
}
248
- const value = initialSearchParams . get ( key ) ?? null
249
- const state = value === null ? null : safeParse ( parse , value , key )
250
+ const query = initialSearchParams . get ( key ) ?? null
251
+ if ( query === queryRef . current ) {
252
+ return
253
+ }
254
+ const state = query === null ? null : safeParse ( parse , query , key )
250
255
debug ( '[nuqs `%s`] syncFromUseSearchParams %O' , key , state )
251
256
stateRef . current = state
257
+ queryRef . current = query
252
258
setInternalState ( state )
253
259
} , [ initialSearchParams ?. get ( key ) , key ] )
254
260
255
261
// Sync all hooks together & with external URL changes
256
262
React . useInsertionEffect ( ( ) => {
257
- function updateInternalState ( state : T | null ) {
263
+ function updateInternalState ( { state, query } : CrossHookSyncPayload ) {
258
264
debug ( '[nuqs `%s`] updateInternalState %O' , key , state )
259
265
stateRef . current = state
266
+ queryRef . current = query
260
267
setInternalState ( state )
261
268
}
262
269
function syncFromURL ( search : URLSearchParams ) {
263
- const value = search . get ( key ) ?? null
264
- const state = value === null ? null : safeParse ( parse , value , key )
270
+ const query = search . get ( key )
271
+ if ( query === queryRef . current ) {
272
+ return
273
+ }
274
+ const state = query === null ? null : safeParse ( parse , query , key )
265
275
debug ( '[nuqs `%s`] syncFromURL %O' , key , state )
266
- updateInternalState ( state )
276
+ updateInternalState ( { state, query } )
267
277
}
268
278
debug ( '[nuqs `%s`] subscribing to sync' , key )
269
279
emitter . on ( SYNC_EVENT_KEY , syncFromURL )
@@ -288,16 +298,16 @@ export function useQueryState<T = string>(
288
298
) {
289
299
newValue = null
290
300
}
291
- // Sync all hooks state (including this one)
292
- emitter . emit ( key , newValue )
293
- enqueueQueryStringUpdate ( key , newValue , serialize , {
301
+ queryRef . current = enqueueQueryStringUpdate ( key , newValue , serialize , {
294
302
// Call-level options take precedence over hook declaration options.
295
303
history : options . history ?? history ,
296
304
shallow : options . shallow ?? shallow ,
297
305
scroll : options . scroll ?? scroll ,
298
306
throttleMs : options . throttleMs ?? throttleMs ,
299
307
startTransition : options . startTransition ?? startTransition
300
308
} )
309
+ // Sync all hooks state (including this one)
310
+ emitter . emit ( key , { state : newValue , query : queryRef . current } )
301
311
return scheduleFlushToURL ( router )
302
312
} ,
303
313
[ key , history , shallow , scroll , throttleMs , startTransition ]
0 commit comments