@@ -7,6 +7,7 @@ import * as React from 'react'
7
7
import { useEventCallback } from '#/hooks/eventCallbackHooks'
8
8
9
9
import LocalStorage , { type LocalStorageData , type LocalStorageKey } from '#/utilities/LocalStorage'
10
+ import { use } from 'react'
10
11
11
12
/** State contained in a `LocalStorageContext`. */
12
13
export interface LocalStorageContextType {
@@ -18,12 +19,21 @@ const LocalStorageContext = React.createContext<LocalStorageContextType>({
18
19
} )
19
20
20
21
/** Props for a {@link LocalStorageProvider}. */
21
- export type LocalStorageProviderProps = Readonly < React . PropsWithChildren >
22
+ export type LocalStorageProviderProps = Readonly < React . PropsWithChildren > & {
23
+ readonly localStorage ?: LocalStorage | undefined
24
+ }
22
25
23
26
/** A React Provider that lets components get the shortcut registry. */
24
27
export default function LocalStorageProvider ( props : LocalStorageProviderProps ) {
25
- const { children } = props
26
- return < > { children } </ >
28
+ const { children, localStorage } = props
29
+
30
+ const finalLocalStorage = localStorage ?? use ( LocalStorageContext ) . localStorage
31
+
32
+ return (
33
+ < LocalStorageContext . Provider value = { { localStorage : finalLocalStorage } } >
34
+ { children }
35
+ </ LocalStorageContext . Provider >
36
+ )
27
37
}
28
38
29
39
/** Exposes a property to get the shortcut registry. */
@@ -68,10 +78,9 @@ export function useLocalStorageState<K extends LocalStorageKey>(
68
78
const { localStorage } = useLocalStorage ( )
69
79
const { sanitize } = options
70
80
71
- const id = React . useId ( )
72
-
73
81
const [ value , privateSetValue ] = React . useState < LocalStorageData [ K ] | undefined > ( ( ) => {
74
82
let savedValue : LocalStorageData [ K ] | undefined = localStorage . get ( key )
83
+
75
84
if ( savedValue !== undefined && sanitize ) {
76
85
savedValue = sanitize ( savedValue )
77
86
}
@@ -85,32 +94,43 @@ export function useLocalStorageState<K extends LocalStorageKey>(
85
94
86
95
const setValue = useEventCallback (
87
96
( newValue : React . SetStateAction < LocalStorageData [ K ] | undefined > ) => {
88
- console . trace ( 'setValue' , key , newValue )
97
+ let nextValue : LocalStorageData [ K ] | undefined = value
98
+
99
+ privateSetValue ( ( currentValue ) => {
100
+ nextValue = typeof newValue === 'function' ? newValue ( currentValue ) : newValue
101
+ return nextValue
102
+ } )
103
+
104
+ // We strictly must update the localStorage value here, because the
105
+ // subscription will trigger (because it triggers since the subscription
106
+ // is created in the `useEffect` below) an update with the old value in
107
+ // state. And this potentially could cause the unintended side effect.
108
+ if ( nextValue === undefined ) {
109
+ localStorage . delete ( key )
110
+ } else {
111
+ localStorage . set ( key , nextValue )
112
+ }
113
+ } ,
114
+ )
115
+
116
+ const updateValueOnLocalStorageChange = useEventCallback (
117
+ ( newValue : LocalStorageData [ K ] | undefined ) => {
118
+ const nextValue = newValue ?? defaultValue
119
+
89
120
privateSetValue ( ( currentValue ) => {
90
- console . log ( 'privateSetValue' , {
91
- currentValue,
92
- newValue,
93
- } )
94
- const nextValue = typeof newValue === 'function' ? newValue ( currentValue ) : newValue
95
-
96
- if ( nextValue === undefined ) {
97
- localStorage . delete ( key )
98
- } else {
99
- localStorage . set ( key , nextValue )
121
+ if ( currentValue === nextValue ) {
122
+ return currentValue
100
123
}
101
124
102
125
return nextValue
103
126
} )
104
127
} ,
105
128
)
106
129
107
- React . useEffect ( ( ) => {
108
- localStorage . lock ( key , id )
109
-
110
- return ( ) => {
111
- localStorage . unlock ( key , id )
112
- }
113
- } , [ key , localStorage , id ] )
130
+ React . useEffect (
131
+ ( ) => localStorage . subscribe ( key , updateValueOnLocalStorageChange ) ,
132
+ [ key , localStorage , updateValueOnLocalStorageChange ] ,
133
+ )
114
134
115
135
return [ value , setValue ]
116
136
}
0 commit comments