snippet
useLocalStorage
snippet
useLocalStorage
Last updated February 13th, 2025
Let's say you wanted to sync state to the local storage of the browser so that you could persist it through a page refresh. How would you go about doing that? Well, this handy hook can help you accomplish that! To be specific, this hook is very similar to useState in that it provides the current state of an item in local storage and a function that helps us set that item.
The useLocalStorage
hook accepts two arguments: 1) a key, which is a string and 2) an initial value which can be any type. We then stringify the initial value using JSON.stringify
since the local storage function only takes strings. Next, we determine if the given key is in the local storage by calling localStorage.getItem
for that key, and if the key exists, we set the state to what is returned from the function call. In the event that the key does not exist, the localStorage.getItem
function call will return null, and we will set the state to the initial value passed into the hook.
Inside the useEffect
we save the current state to the local storage, using the key passed into the hook as the key and the current state as the value. It's important to know that this hook re-runs every time the key or state changes.
We finally return an array where the first item is the current state (i.e. an item in local storage) and the second item is a setter function we can use for saving items to local storage.
As you can when we call useLocalStorage
in our app, we pass in a key and an initial value. The hook then returns the current state of the local storage for that key and a setter function we can use for saving items to local storage for a given key.
It's a bummer but the local storage function only exists on the window
object within the browser thus, it cannot be accessed on any server-rendered pages. This means that the useLocalStorage
hook as defined above will not work on server-rendered pages -- only client-rendered pages.
By using this hook with frameworks like Next.js (specifically the app router), you may encounter an error that says window is not defined
. With this in mind, we've augmented the hook to be server friendly by checking if the window object exists before running the hook. See below:
import { useState, useEffect } from "react";
const useLocalStorage = (key, initValue) => {
const stringInitValue = JSON.stringify(initValue);
const [state, setState] = useState(
JSON.parse(window.localStorage.getItem(key) ?? stringInitValue)
);
useEffect(() => {
window.localStorage.setItem(key, JSON.stringify(state));
}, [key, state]);
return [state, setState];
};
import { useState, useEffect } from "react";
const useLocalStorage = (key, initValue) => {
const isBrowser = typeof window !== "undefined";
const [state, setState] = useState(undefined);
useEffect(() => {
if (!isBrowser) return;
const storedValue = localStorage.getItem(key);
setState(storedValue ? JSON.parse(storedValue) : initValue);
}, [key, isBrowser]);
useEffect(() => {
if (state !== undefined && isBrowser) {
localStorage.setItem(key, JSON.stringify(state));
}
}, [key, state, isBrowser]);
return [state ?? initValue, setState];
};
import useLocalStorage from "./useLocalStorage";
const App = () => {
const [theme, setTheme] = useLocalStorage("theme-key", {
themeColour: "dark",
});
return (
<>
{theme.themeColour === "dark" ? (
<div className="dark-theme">
<button onClick={() => setTheme({ themeColour: "light" })}>
Change the theme to light
</button>
</div>
) : (
<div className="light-theme"></div>
)}
</>
);
};