I am trying to create a replacement of useState
in next.js resilient to page refreshes.
One of the possible solutions that came across was to use window.localStorage
to save and retrieve the state. That would make state persistent even after page refreshes.
I found the following implementation of a useLocalStorage
hook for ReactJS https://usehooks.com/useLocalStorage/
function useLocalStorage(key, initialValue) {
// State to store our value
// Pass initial state function to useState so logic is only executed once
const [storedValue, setStoredValue] = useState(() => {
if (typeof window === "undefined") {
return initialValue;
}
try {
// Get from local storage by key
const item = window.localStorage.getItem(key);
// Parse stored json or if none return initialValue
return item ? JSON.parse(item) : initialValue;
} catch (error) {
// If error also return initialValue
console.log(error);
return initialValue;
}
});
// Return a wrapped version of useState's setter function that ...
// ... persists the new value to localStorage.
const setValue = (value) => {
try {
// Allow value to be a function so we have same API as useState
const valueToStore =
value instanceof Function ? value(storedValue) : value;
// Save state
setStoredValue(valueToStore);
// Save to local storage
if (typeof window !== "undefined") {
window.localStorage.setItem(key, JSON.stringify(valueToStore));
}
} catch (error) {
// A more advanced implementation would handle the error case
console.log(error);
}
};
return [storedValue, setValue];
}
However, it generates the following error when I use it in NextJS:
Uncaught Error: Hydration failed because the initial UI does not match what was rendered on the server
After a little search I found out that the window
object doesn't exist in the (Next.js) server side and this is the possible cause of the error (Window is not defined in Next.js React app).
A possible solution is to protect the usage of window
with the useEffect
hook, that only runs on the client side.
My current implementation of the useLocalStorage
hook is
function useLocalStorage<T>(key: string, defaultValue: T): [T, Dispatch<SetStateAction<T>>] {
const [value, setValue] = useState<T>(defaultValue);
useEffect(() => {
try {
const item = window.localStorage.getItem(key);
setValue(item ? JSON.parse(item) : defaultValue);
}
catch (error) {
setValue(defaultValue);
}
}, [key, defaultValue]);
useEffect(() => {
window.localStorage.setItem(key, JSON.stringify(value));
}, [key, value]);
return [value, setValue];
};
However, this time the hook doesn't always work as expected because the order of execution of the useEffect callbacks is not guaranteed. As a consequence, sometimes the state is lost.
I want to know what would be a correct implementation for this in NextJS and understand where the logic of my code is failing.