Prompt
Write a custom useLocalStorage hook that syncs state with localStorage. It should work like useState but persist the value across page reloads.
Solution
This hook is an interview favorite because it packs three senior concerns into a dozen lines: initialization cost, API parity, and failure at a trust boundary.
Lazy initializer: useState(() => …) runs the function only on mount. Passing useState(readAndParse()) would hit localStorage and JSON.parse on every render — the result gets thrown away after the first, but you pay the synchronous I/O each time. The initializer-function form is precisely for expensive initial state.
API parity: useState's contract includes functional updates, so setValue checks value instanceof Function and applies it to the current value before persisting. Skip this and setCount(c => c + 1) stores a stringified function — the hook silently stops being a drop-in replacement.
The try/catch is not defensive noise: localStorage genuinely throws — SSR has no window, Safari private mode used to throw on write, quota can be exceeded, and stored JSON can be corrupt. Falling back to initialValue keeps the app rendering when storage is hostile. React state stays the source of truth; storage is a write-through side effect.
Red flag: Reading localStorage directly in render without the lazy wrapper, or forgetting serialization entirely. Both say "I've never shipped this hook."
Say it: "Lazy initializer so storage is read once, functional-update support to keep useState's contract, and try/catch because storage throws in SSR and private mode — state is the source of truth, storage is write-through."