usehooks
usehooks copied to clipboard
Add useMemo call to useLocalStorage so that the value returned only changes if store actually changes.
At the moment if a component that uses useLocalStorage rerenders then useLocalStorage will call JSON.parse(store) as a result of the rerender. The value of store won't have changed, but the new call to JSON.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 to JSON.parse with the same store value.
our current awful workaround
hope this pr gets merged
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().
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;
This hook should accept an initialValue being passed.
@lynnandtonic @tylermcginnis Any chance we can get this merged?
Is this package unmaintained?