Building a useLocalStorage Hook
In this page:
function useLocalStorage(key, initialValue) {
const [value, setValue] = useState(() => {
// read the saved value or use initialValue
});
useEffect(() => {
// write value back to localStorage
}, [key, value]);
return [value, setValue];
}
Why Sync State with localStorage
Normal React state resets every time the page reloads, since it only lives in memory. localStorage persists data in the browser even after the tab is closed.
A useLocalStorage hook combines the two, so a value behaves like normal state but also survives a refresh.
- Good use cases are:
- theme preference
- a saved draft
- 'remember me' style settings — anything you'd want to survive a reload.
Example: Why Sync State with localStorage
<!DOCTYPE html>
<html>
<head>
<script src="https://unpkg.com/react@18/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
</head>
<body>
<div id="root"></div>
<script type="text/babel">
function useLocalStorage(key, initialValue) {
const [value, setValue] = React.useState(() => {
const saved = localStorage.getItem(key);
return saved !== null ? JSON.parse(saved) : initialValue;
});
React.useEffect(() => {
localStorage.setItem(key, JSON.stringify(value));
}, [key, value]);
return [value, setValue];
}
function App() {
const [name, setName] = useLocalStorage("name", "Guest");
return <input value={name} onChange={e => setName(e.target.value)} />;
}
ReactDOM.createRoot(document.getElementById('root')).render(<App />);
</script>
</body>
</html>
Reading the Saved Value on First Render
useState accepts a function as its initial value (called a lazy initializer), which only runs once on the first render. This is the right place to check localStorage for a previously saved value, since checking it directly in the function body would run on every render.
Example: Reading the Saved Value on First Render
<!DOCTYPE html>
<html>
<head>
<script src="https://unpkg.com/react@18/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
</head>
<body>
<div id="root"></div>
<script type="text/babel">
function useLocalStorage(key, initialValue) {
const [value] = React.useState(() => {
try {
const saved = localStorage.getItem(key);
return saved !== null ? JSON.parse(saved) : initialValue;
} catch { return initialValue; }
});
return value;
}
function App() {
const count = useLocalStorage("count", 0);
return <p>Loaded count: {count}</p>;
}
ReactDOM.createRoot(document.getElementById('root')).render(<App />);
</script>
</body>
</html>
Writing Back on Every Change
A useEffect that watches the state value writes it back to localStorage every time it changes, using JSON.stringify since localStorage can only store strings. This keeps the saved copy always in sync with the current React state.
Example: Writing Back on Every Change
<!DOCTYPE html>
<html>
<head>
<script src="https://unpkg.com/react@18/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
</head>
<body>
<div id="root"></div>
<script type="text/babel">
function useLocalStorage(key, initialValue) {
const [value, setValue] = React.useState(initialValue);
React.useEffect(() => { localStorage.setItem(key, JSON.stringify(value)); }, [key, value]);
return [value, setValue];
}
function App() {
const [notes, setNotes] = useLocalStorage("notes", "");
return (
<div>
<textarea value={notes} onChange={e => setNotes(e.target.value)} />
<p>Saved: {notes.length} characters</p>
</div>
);
}
ReactDOM.createRoot(document.getElementById('root')).render(<App />);
</script>
</body>
</html>
- Forgetting that localStorage only stores strings, so objects/arrays must be JSON.stringify'd before saving and JSON.parse'd when reading back.
- Not wrapping localStorage access in a try/catch, which can throw in private browsing mode or when storage is full.
- Using the same localStorage key for two different pieces of state, causing them to silently overwrite each other.
- useLocalStorage is a custom hook that syncs a piece of React state with the browser's localStorage.
- It reads the saved value on initial render and writes back to localStorage whenever the state changes.
- JSON.stringify/JSON.parse are needed because localStorage only stores strings.
- This pattern is commonly used for theme preferences, form drafts, and small settings that should survive a page refresh.
Available since React 16.8 (Hooks introduction); localStorage itself is supported in all modern browsers (IE8+).
Chapter Quiz — Complete all 10 topics to unlock
0/10 topics done
Complete these topics first: