State
useSessionStorage
useState backed by sessionStorage, synced across tabs.
Demo
live · imported from hookli
Stored under hookli-docs-draft — survives a reload, cleared when the tab closes.
import { useSessionStorage } from "hookli";
export function Demo() {
const [draft, setDraft, removeDraft] = useSessionStorage("draft", "");
return (
<div>
<input value={draft} onChange={(e) => setDraft(e.target.value)} />
<button type="button" onClick={removeDraft}>Clear</button>
</div>
);
}Usage
import { useSessionStorage } from "hookli";
export function Demo() {
const [draft, setDraft, removeDraft] = useSessionStorage("draft", "");
return (
<div>
<input value={draft} onChange={(e) => setDraft(e.target.value)} />
<button type="button" onClick={removeDraft}>Clear</button>
</div>
);
}API
useSessionStorage<T>(key: string, initialValue: T | (() => T), options?: UseSessionStorageOptions<T>): [T, (value: T | ((prev: T) => T)) => void, () => void]Parameters
| Name | Type | Default | Description |
|---|---|---|---|
| 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<T> | {} | Optional custom serializer/deserializer and hydration flag. |
Returns
| Name | Type | Description |
|---|---|---|
| [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. |
UseSessionStorageOptions<T>
Custom (de)serialization and hydration behaviour.
| Name | Type | Description |
|---|---|---|
| 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 | Read sessionStorage synchronously on mount. Set false to defer to after hydration and avoid SSR mismatches. |
Hook
The implementation, straight from hookli. Copy it, or install the package.
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<T> {
serializer?: (value: T) => string;
deserializer?: (value: string) => T;
initializeWithValue?: boolean;
}
type UseSessionStorageReturn<T> = [
T,
(value: T | ((prev: T) => T)) => void,
() => void,
];
const IS_SERVER = typeof window === "undefined";
export function useSessionStorage<T>(
key: string,
initialValue: T | (() => T),
options: UseSessionStorageOptions<T> = {},
): UseSessionStorageReturn<T> {
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<T>(() =>
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];
}