Using a useLocalStorage hook and rehydration
Recently I ran into an issue using the useLocalStorage hook from useHooks.com — which is brills by the way!
The Problem
The issue I was seeing was weird, React would mess up the way my DOM nodes were ordered resulting in some very odd
looking layouts. The problem ocurred after i’d used useLocalStorage
to “set state” and I then refreshed the page.
To give you some context, if you pop over to https://gatsbyconf.com/ and register, you’ll be given a super duper raffle code. If users didn’t write this code down I was concerned they’d lose it. One solution is to persist the state of the UI so even if a user closes their browser tab, the next time they come back to the site the raffle code would still be displayed.
This worked fine if you followed the journey, E.g register, let React re-render and show the raffle code, but it messed up if you refreshed the page.
The reason is, the useLocalStorage
hook has a different initialValue
on refresh than it does on “set state”.
The Solution
Luckily Ward from Gatsby’s engineering team came up with the following solution and i’ll do my best to explain it.
- The
storedValue
is set to theinitialValue
and thetry / catch
has been moved to auseEffect
- The
useEffect
is set to run “on mount” and grabs anylocalStorage
values and then uses them tosetStoredValue
This results in React re-rendering when both the value is set manually and when the component mounts.
Here’s the diff, hope it helps, of if you’re looking for the src
here’s a Gist
import { useState } from "react";
const useLocalStorage(key, initialValue) => {
+ const [storedValue, setStoredValue] = useState(initialValue)
- const [storedValue, setStoredValue] = useState(() => {
- try {
- const item = window.localStorage.getItem(key);
- return item ? JSON.parse(item) : initialValue;
- } catch (error) {
- console.log(error);
- return initialValue;
- }
- });
const setValue = (value) => {
try {
const valueToStore = value instanceof Function ? value(storedValue) : value;
setStoredValue(valueToStore);
window.localStorage.setItem(key, JSON.stringify(valueToStore));
} catch (error) {
console.log(error);
}
};
+ useEffect(() => {
+ try {
+ const item = window.localStorage.getItem(key)
+ setStoredValue(item ? JSON.parse(item) : initialValue)
+ } catch (error) {
+ console.log(error)
+ return setStoredValue(initialValue)
+ }
+ }, [])
return [storedValue, setValue];
}