# useSessionStorage > useState backed by sessionStorage, synced across tabs. - Category: State - Package: `hookli` (install with `npm i hookli`) - Docs: https://hookli.vercel.app/docs/use-session-storage ## Signature ```ts useSessionStorage(key: string, initialValue: T | (() => T), options?: UseSessionStorageOptions): [T, (value: T | ((prev: T) => T)) => void, () => void] ``` ## Parameters - `key`: `string` — The sessionStorage key to read and write. - `initialValue`: `T | (() => T)` — Value used before hydration and when the key is empty. Pass a function to compute it lazily. - `options`: `UseSessionStorageOptions` (default: `{}`) — Optional custom serializer/deserializer and hydration flag. ## Returns - `[0] value`: `T` — The stored value. Server-rendered as initialValue, then hydrated from sessionStorage after mount. - `[1] setValue`: `(value: T | ((prev: T) => T)) => void` — Persists to sessionStorage and updates state; accepts a value or an updater. Syncs every hook using the key in this tab. - `[2] removeValue`: `() => void` — Removes the key from sessionStorage and resets state to initialValue. ## Types ### UseSessionStorageOptions Custom (de)serialization and hydration behaviour. - `serializer`: `(value: T) => string` — Turn the value into the string stored under the key. Defaults to JSON.stringify. - `deserializer`: `(value: string) => T` — Parse the stored string back into a value. Defaults to JSON.parse. - `initializeWithValue`: `boolean` (default: `true`) — Read sessionStorage synchronously on mount. Set false to defer to after hydration and avoid SSR mismatches. ## Usage ```tsx import { useSessionStorage } from "hookli"; export function Demo() { const [draft, setDraft, removeDraft] = useSessionStorage("draft", ""); return (
setDraft(e.target.value)} />
); } ``` ## Source `src/hooks/use-session-storage/use-session-storage.ts` ```ts import { useCallback, useEffect, useState } from "react"; import { useEventCallback } from "../use-event-callback/use-event-callback"; import { useEventListener } from "../use-event-listener/use-event-listener"; interface UseSessionStorageOptions { serializer?: (value: T) => string; deserializer?: (value: string) => T; initializeWithValue?: boolean; } type UseSessionStorageReturn = [ T, (value: T | ((prev: T) => T)) => void, () => void, ]; const IS_SERVER = typeof window === "undefined"; export function useSessionStorage( key: string, initialValue: T | (() => T), options: UseSessionStorageOptions = {}, ): UseSessionStorageReturn { const { initializeWithValue = true } = options; const serializer = useCallback( (value: T) => { if (options.serializer) return options.serializer(value); return JSON.stringify(value); }, [options], ); const deserializer = useCallback( (value: string): T => { if (options.deserializer) return options.deserializer(value); const defaultValue = initialValue instanceof Function ? initialValue() : initialValue; if (value === "undefined") return defaultValue; try { return JSON.parse(value); } catch { return defaultValue; } }, [options, initialValue], ); const readValue = useCallback((): T => { const initial = initialValue instanceof Function ? initialValue() : initialValue; if (IS_SERVER) return initial; try { const raw = window.sessionStorage.getItem(key); return raw ? deserializer(raw) : initial; } catch { return initial; } }, [initialValue, key, deserializer]); const [storedValue, setStoredValue] = useState(() => initializeWithValue ? readValue() : initialValue instanceof Function ? initialValue() : initialValue, ); const setValue = useEventCallback((value: T | ((prev: T) => T)) => { if (IS_SERVER) return; try { const newValue = value instanceof Function ? value(readValue()) : value; window.sessionStorage.setItem(key, serializer(newValue)); setStoredValue(newValue); window.dispatchEvent(new StorageEvent("session-storage", { key })); } catch { return; } }); const removeValue = useEventCallback(() => { if (IS_SERVER) return; const defaultValue = initialValue instanceof Function ? initialValue() : initialValue; window.sessionStorage.removeItem(key); setStoredValue(defaultValue); window.dispatchEvent(new StorageEvent("session-storage", { key })); }); useEffect(() => { setStoredValue(readValue()); }, [key]); const handleStorageChange = useCallback( (event: StorageEvent) => { if (event.key && event.key !== key) return; setStoredValue(readValue()); }, [key, readValue], ); useEventListener("storage", handleStorageChange); useEventListener("session-storage", handleStorageChange); return [storedValue, setValue, removeValue]; } ```