Skip to content

Commit 4aae1b3

Browse files
Fix localStorage Provider
1 parent 49dc8db commit 4aae1b3

File tree

1 file changed

+43
-23
lines changed

1 file changed

+43
-23
lines changed

app/gui/src/dashboard/providers/LocalStorageProvider.tsx

Lines changed: 43 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import * as React from 'react'
77
import { useEventCallback } from '#/hooks/eventCallbackHooks'
88

99
import LocalStorage, { type LocalStorageData, type LocalStorageKey } from '#/utilities/LocalStorage'
10+
import { use } from 'react'
1011

1112
/** State contained in a `LocalStorageContext`. */
1213
export interface LocalStorageContextType {
@@ -18,12 +19,21 @@ const LocalStorageContext = React.createContext<LocalStorageContextType>({
1819
})
1920

2021
/** 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+
}
2225

2326
/** A React Provider that lets components get the shortcut registry. */
2427
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+
)
2737
}
2838

2939
/** Exposes a property to get the shortcut registry. */
@@ -68,10 +78,9 @@ export function useLocalStorageState<K extends LocalStorageKey>(
6878
const { localStorage } = useLocalStorage()
6979
const { sanitize } = options
7080

71-
const id = React.useId()
72-
7381
const [value, privateSetValue] = React.useState<LocalStorageData[K] | undefined>(() => {
7482
let savedValue: LocalStorageData[K] | undefined = localStorage.get(key)
83+
7584
if (savedValue !== undefined && sanitize) {
7685
savedValue = sanitize(savedValue)
7786
}
@@ -85,32 +94,43 @@ export function useLocalStorageState<K extends LocalStorageKey>(
8594

8695
const setValue = useEventCallback(
8796
(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+
89120
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
100123
}
101124

102125
return nextValue
103126
})
104127
},
105128
)
106129

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+
)
114134

115135
return [value, setValue]
116136
}

0 commit comments

Comments
 (0)