1
- import { useCallback , useEffect , useState } from 'react' ;
1
+ import { useCallback , useEffect , useMemo } from 'react' ;
2
2
import { useHistory , useLocation } from 'react-router' ;
3
3
4
- type UseQueryParamsStateReturnType < T > = [ T | undefined , ( newValue : T | undefined ) => void ] ;
4
+ type UseQueryParamsStateReturnType < T > = [
5
+ T | undefined ,
6
+ ( newValue : T | undefined , params ?: { replace ?: boolean } ) => void
7
+ ] ;
5
8
6
9
/**
7
10
* Custom hook to manage a state synchronized with a URL query parameter
@@ -18,64 +21,45 @@ export function useQueryParamsState<T extends string | undefined>(
18
21
param : string ,
19
22
initialState : T
20
23
) : UseQueryParamsStateReturnType < T > {
21
- const location = useLocation ( ) ;
24
+ const { search } = useLocation ( ) ;
22
25
const history = useHistory ( ) ;
23
26
24
- // State for managing the value derived from the query parameter
25
- const [ value , setValue ] = useState < T | undefined > ( ( ) => {
26
- const { search } = location ;
27
- const searchParams = new URLSearchParams ( search ) ;
28
- const paramValue = searchParams . get ( param ) ;
27
+ const value = useMemo ( ( ) => {
28
+ const params = new URLSearchParams ( search ) ;
29
+ return ( params . get ( param ) ?? undefined ) as T | undefined ;
30
+ } , [ search , param ] ) ;
29
31
30
- return paramValue !== null ? ( decodeURIComponent ( paramValue ) as T ) : undefined ;
31
- } ) ;
32
-
33
- // Update the value from URL to state
34
- useEffect ( ( ) => {
35
- const searchParams = new URLSearchParams ( location . search ) ;
36
- const paramValue = searchParams . get ( param ) ;
37
-
38
- if ( paramValue !== null ) {
39
- const decodedValue = decodeURIComponent ( paramValue ) as T ;
40
- setValue ( decodedValue ) ;
41
- } else {
42
- setValue ( undefined ) ;
43
- }
44
- } , [ location . search ] ) ;
45
-
46
- // Set the value from state to URL
47
- useEffect ( ( ) => {
48
- const currentSearchParams = new URLSearchParams ( location . search ) ;
49
-
50
- if ( value && currentSearchParams . get ( param ) === encodeURIComponent ( value ) ) return ;
51
-
52
- // Update the query parameter with the current state value
53
- if ( value !== null && value !== '' && value !== undefined ) {
54
- currentSearchParams . set ( param , encodeURIComponent ( value ) ) ;
55
- } else {
56
- currentSearchParams . delete ( param ) ;
57
- }
58
-
59
- // Update the URL with the modified search parameters
60
- const newUrl = [ location . pathname , currentSearchParams . toString ( ) ] . filter ( Boolean ) . join ( '?' ) ;
61
-
62
- history . push ( newUrl ) ;
63
- } , [ param , value ] ) ;
64
-
65
- // Initi state with initial state value
66
- useEffect ( ( ) => {
67
- setValue ( initialState ) ;
68
- } , [ ] ) ;
69
-
70
- const handleSetValue = useCallback (
71
- ( newValue : T | undefined ) => {
32
+ const setValue = useCallback (
33
+ ( newValue : T | undefined , params : { replace ?: boolean } = { } ) => {
72
34
if ( newValue !== undefined && typeof newValue !== 'string' ) {
73
35
throw new Error ( "useQueryParamsState: Can't set a value to something that isn't a string" ) ;
74
36
}
75
- setValue ( newValue ) ;
37
+
38
+ // Create new search params
39
+ const newParams = new URLSearchParams ( history . location . search ) ;
40
+ if ( newValue === undefined ) {
41
+ newParams . delete ( param ) ;
42
+ } else {
43
+ newParams . set ( param , newValue ) ;
44
+ }
45
+
46
+ // Apply new search params
47
+ const newSearch = '?' + newParams ;
48
+ if ( params . replace ) {
49
+ history . replace ( newSearch ) ;
50
+ } else {
51
+ history . push ( newSearch ) ;
52
+ }
76
53
} ,
77
- [ setValue ]
54
+ [ history . location . search , param ]
78
55
) ;
79
56
80
- return [ value , handleSetValue ] ;
57
+ // Apply initialState if any
58
+ useEffect ( ( ) => {
59
+ if ( initialState && ! value ) {
60
+ setValue ( initialState , { replace : true } ) ;
61
+ }
62
+ } , [ initialState ] ) ;
63
+
64
+ return [ value , setValue ] ;
81
65
}
0 commit comments