We use cookies for site analytics. Accept to help us understand how the site is used. See our Privacy Policy for details.
A medium frontend problem, graded against 5 test cases (2 of them hidden).
React and browser-platform problems - hooks, event handling, and rendering behaviour.
Reach for it when you see: A component, hook, or DOM-behaviour question rather than an algorithm.
More Frontendproblems →```tsx
export function useLocalStorage<T>(key: string, initial: T) {
const isClient = typeof window !== "undefined";
const [value, setValue] = useState<T>(() => {
if (!isClient) return initial;
try {
const raw = window.localStorage.getItem(key);
return raw ? (JSON.parse(raw) as T) : initial;
} catch {
return initial;
}
});
// Persist on change
useEffect(() => {
if (!isClient) return;
try {
window.localStorage.setItem(key, JSON.stringify(value));
} catch {
// quota exceeded / private mode - fail silently or surface
}
}, [key, value, isClient]);
// Cross-tab sync
useEffect(() => {
if (!isClient) return;
function onStorage(e: StorageEvent) {
if (e.key !== key || e.newValue == null) return;
try {
setValue(JSON.parse(e.newValue) as T);
} catch { /* ignore */ }
}
window.addEventListener("storage", onStorage);
return () => window.removeEventListener("storage", onStorage);
}, [key, isClient]);
return [value, setValue] as const;
}
```
Why each piece matters:
- `typeof window !== "undefined"` guard - Next.js, Remix, etc. run components on the server. Touching `localStorage` there throws.
- Lazy initial state (`useState(() => ...)`) - read `localStorage` only once on mount, not on every render.
- The `storage` event fires in *other* tabs when the value changes in *this* one. Without the listener, two open tabs drift apart.
- Try/catch on `setItem` - quota exceeded (5-10MB typical) is real, especially in Safari private mode (0 quota).
When to use `sessionStorage` instead: data should not survive tab close (e.g. multi-step form state).
The full reference solution in every supported language stays in the editor above - reveal it there once you have had a real attempt.
Read off this problem's own test suite, so these are the cases a submission actually has to survive.
These apply to the pattern as a whole, not just this problem.