-
Notifications
You must be signed in to change notification settings - Fork 570
Add useMemo call to useLocalStorage so that the value returned only changes if store actually changes. #304
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
...hanges if store actually changes.
joshxyzhimself
commented
Jul 1, 2024
RobertRad
commented
Jul 19, 2024
We ran into the same problem - somewhat similar with a JWT, on which a some other hooks depend.
I think data in the localStorage might be some basic values (authorization, language) which is used (directly or derived) in a lot of parts of the application. So this trade-off between memory & CPU should be worth using the useMemo()
.
joshxyzhimself
commented
Jul 19, 2024
update on my workaround, no issues so far.
/** * * If active tab updates localStorage: * - At current tab, localStorage is updated. * - At current tab, "storage" event is emitted. * - At current tab, window receives "storage" event. * - At current tab, state is updated. * * If inactive tab updates localStorage: * - At inactive tab, localStorage is updated. * - At current tab, window receives "storage" event. * - At current tab, state is updated. * */ import { useCallback, useEffect, useMemo, useState } from "react"; export function useLocalStorage<T>(key: string) { const [state, set_state] = useState<T | null>(() => { const unparsed = localStorage.getItem(key); if (typeof unparsed === "string") { const parsed = JSON.parse(unparsed) as T; return parsed; } return null; }); useEffect(() => { const listener = (e: StorageEvent) => { if (e.key === key) { if (typeof e.newValue === "string") { const parsed = JSON.parse(e.newValue) as T; set_state(parsed); } } }; window.addEventListener("storage", listener); return () => { window.removeEventListener("storage", listener); }; }, [key]); /** * @description dispatches an event so all instances gets updated. */ const set_stored = useCallback( (value: T | null) => { const newValue = JSON.stringify(value); localStorage.setItem(key, newValue); const event = new StorageEvent("storage", { key, newValue }); dispatchEvent(event); }, [key], ); return useMemo( () => [state, set_stored] as [T | null, (value: T | null) => void], [state, set_stored], ); } export default useLocalStorage;
import useLocalStorage from "./useLocalStorage"; export interface Session { id: string; iss: string; aud: string; sub: string; iat: number; nbf: number; exp: number; } export const useSession = () => { return useLocalStorage<Session>("session"); }; export default useSession;
pedroapfilho
commented
Jan 22, 2025
This hook should accept an initialValue being passed.
yunyu
commented
Jan 28, 2025
@lynnandtonic @tylermcginnis Any chance we can get this merged?
RutsuKun
commented
Jun 8, 2025
Is this package unmaintained?
At the moment if a component that uses
useLocalStorage
rerenders thenuseLocalStorage
will callJSON.parse(store)
as a result of the rerender. The value ofstore
won't have changed, but the new call toJSON.parse
will mean that a new value is returned with identical contents to the old value. Any hooks then using that value in their deps array will then have to be run again, even though the actual data is the same.I've updated useLocalStorage so that it uses
useMemo
to avoid repeated calls toJSON.parse
with the same store value.