A React hook for persisting state in sessionStorage
npm install @usefy/use-session-storage

A lightweight React hook for persisting state in sessionStorage with automatic same-tab synchronization
Installation •
Quick Start •
API Reference •
Examples •
License
---
@usefy/use-session-storage provides a useState-like API for persisting data in sessionStorage. Features include same-tab component synchronization, custom serialization, lazy initialization, and error handling. Data persists during the browser session (tab lifetime) but clears when the tab is closed. Each tab has isolated storage, making it perfect for temporary form data, wizard steps, and session-specific state.
Part of the @usefy ecosystem — a collection of production-ready React hooks designed for modern applications.
- Zero Dependencies — Pure React implementation with no external dependencies
- TypeScript First — Full type safety with generics and exported interfaces
- useState-like API — Familiar tuple return: [value, setValue, removeValue]
- Same-Tab Sync — Multiple components using the same key stay in sync automatically
- Tab Isolation — Each browser tab has its own session storage
- React 18+ Optimized — Built with useSyncExternalStore for Concurrent Mode compatibility
- Auto-Cleanup — Data cleared automatically when tab closes
- Custom Serialization — Support for Date, Map, Set, or any custom type
- Lazy Initialization — Function initializer support for expensive defaults
- Error Handling — onError callback for graceful error recovery
- SSR Compatible — Works seamlessly with Next.js, Remix, and other SSR frameworks
- Stable References — Memoized functions for optimal performance
- Well Tested — Comprehensive test coverage with Vitest
| Feature | localStorage | sessionStorage |
| ---------------- | ------------------------ | ------------------------- |
| Data persistence | Until explicitly cleared | Until tab closes |
| Tab sharing | Shared across all tabs | Isolated per tab |
| Best for | User preferences, themes | Form drafts, wizard steps |
---
``bashnpm
npm install @usefy/use-session-storage
$3
This package requires React 18 or 19:
`json
{
"peerDependencies": {
"react": "^18.0.0 || ^19.0.0"
}
}
`---
Quick Start
`tsx
import { useSessionStorage } from "@usefy/use-session-storage";function CheckoutForm() {
const [formData, setFormData, clearForm] = useSessionStorage(
"checkout-form",
{
name: "",
email: "",
address: "",
}
);
return (
);
}
`---
API Reference
$3
A hook that persists state in sessionStorage for the duration of the browser session.
#### Parameters
| Parameter | Type | Description |
| -------------- | ----------------------------- | ------------------------------------------ |
|
key | string | The sessionStorage key |
| initialValue | T \| () => T | Initial value or lazy initializer function |
| options | UseSessionStorageOptions | Configuration options |#### Options
| Option | Type | Default | Description |
| -------------- | ------------------------ | ---------------- | ---------------------------- |
|
serializer | (value: T) => string | JSON.stringify | Custom serializer function |
| deserializer | (value: string) => T | JSON.parse | Custom deserializer function |
| onError | (error: Error) => void | — | Callback for error handling |#### Returns
[T, SetValue| Index | Type | Description |
| ----- | ----------------------------- | --------------------------------------------- |
|
[0] | T | Current stored value |
| [1] | Dispatch | Function to update value (same as useState) |
| [2] | () => void | Function to remove value and reset to initial |---
Examples
$3
`tsx
import { useSessionStorage } from "@usefy/use-session-storage";// Multiple components using the same key automatically stay in sync!
function WizardProgress() {
const [step] = useSessionStorage("wizard-step", 1);
return ;
}
function WizardNavigation() {
const [step, setStep] = useSessionStorage("wizard-step", 1);
return (
);
}function WizardContent() {
const [step] = useSessionStorage("wizard-step", 1);
// Also updates when WizardNavigation changes step!
return ;
}
`$3
`tsx
import { useSessionStorage } from "@usefy/use-session-storage";function SignupWizard() {
const [step, setStep] = useSessionStorage("signup-step", 1);
const [formData, setFormData, resetForm] = useSessionStorage("signup-data", {
email: "",
password: "",
profile: {},
});
const handleNext = () => setStep((prev) => prev + 1);
const handleBack = () => setStep((prev) => prev - 1);
const handleComplete = async () => {
await submitSignup(formData);
resetForm();
setStep(1);
};
return (
Step {step} of 3
{step === 1 && (
value={formData.email}
onChange={(email) => setFormData((prev) => ({ ...prev, email }))}
onNext={handleNext}
/>
)}
{step === 2 && (
value={formData.password}
onChange={(password) =>
setFormData((prev) => ({ ...prev, password }))
}
onBack={handleBack}
onNext={handleNext}
/>
)}
{step === 3 && (
value={formData.profile}
onChange={(profile) => setFormData((prev) => ({ ...prev, profile }))}
onBack={handleBack}
onComplete={handleComplete}
/>
)}
);
}
`$3
`tsx
import { useSessionStorage } from "@usefy/use-session-storage";function ContactForm() {
const [draft, setDraft, clearDraft] = useSessionStorage("contact-draft", {
subject: "",
message: "",
});
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
await sendMessage(draft);
clearDraft(); // Clear after successful submit
};
return (
);
}
`$3
`tsx
import { useSessionStorage } from "@usefy/use-session-storage";interface CartItem {
id: string;
name: string;
quantity: number;
}
function TabCart() {
const [cart, setCart, clearCart] = useSessionStorage(
"tab-cart",
[]
);
const addItem = (product: Product) => {
setCart((prev) => {
const existing = prev.find((item) => item.id === product.id);
if (existing) {
return prev.map((item) =>
item.id === product.id
? { ...item, quantity: item.quantity + 1 }
: item
);
}
return [...prev, { id: product.id, name: product.name, quantity: 1 }];
});
};
return (
Cart items: {cart.length}
This cart is specific to this tab only
);
}
`$3
`tsx
import { useSessionStorage } from "@usefy/use-session-storage";function ProtectedPage() {
const [token, setToken, clearToken] = useSessionStorage(
"auth-token",
null
);
const login = async (credentials: Credentials) => {
const response = await authenticate(credentials);
setToken(response.token);
};
const logout = () => {
clearToken();
// Token is automatically cleared when tab closes
};
if (!token) {
return ;
}
return (
You are logged in (this session only)
);
}
`$3
`tsx
import { useSessionStorage } from "@usefy/use-session-storage";function SessionTimer() {
const [sessionStart] = useSessionStorage("session-start", new Date(), {
serializer: (date) => date.toISOString(),
deserializer: (str) => new Date(str),
});
const [elapsed, setElapsed] = useState(0);
useEffect(() => {
const interval = setInterval(() => {
setElapsed(Math.floor((Date.now() - sessionStart.getTime()) / 1000));
}, 1000);
return () => clearInterval(interval);
}, [sessionStart]);
return
Session duration: {elapsed} seconds;
}
`$3
`tsx
import { useSessionStorage } from "@usefy/use-session-storage";function RobustSessionStorage() {
const [data, setData] = useSessionStorage(
"session-data",
{ items: [] },
{
onError: (error) => {
console.error("Session storage error:", error.message);
toast.error("Failed to save session data");
},
}
);
return ;
}
`$3
`tsx
import { useSessionStorage } from "@usefy/use-session-storage";function ExpensiveDefaultDemo() {
// Expensive computation only runs if no stored value exists
const [cache, setCache] = useSessionStorage("session-cache", () => {
console.log("Building initial cache...");
return buildExpensiveCache();
});
return ;
}
`$3
`tsx
import { useSessionStorage } from "@usefy/use-session-storage";interface QuizState {
currentQuestion: number;
answers: Record;
startTime: number;
}
function Quiz() {
const [quiz, setQuiz, resetQuiz] = useSessionStorage(
"quiz-progress",
{
currentQuestion: 0,
answers: {},
startTime: Date.now(),
}
);
const submitAnswer = (answer: string) => {
setQuiz((prev) => ({
...prev,
answers: { ...prev.answers, [prev.currentQuestion]: answer },
currentQuestion: prev.currentQuestion + 1,
}));
};
const handleComplete = async () => {
await submitQuiz(quiz.answers);
resetQuiz();
};
return (
Question {quiz.currentQuestion + 1} of 10
question={questions[quiz.currentQuestion]}
onAnswer={submitAnswer}
/>
);
}
`---
TypeScript
This hook is written in TypeScript with full generic support.
`tsx
import {
useSessionStorage,
type UseSessionStorageOptions,
type UseSessionStorageReturn,
type InitialValue,
} from "@usefy/use-session-storage";// Generic type inference
const [name, setName] = useSessionStorage("name", "Guest"); // string
const [step, setStep] = useSessionStorage("step", 1); // number
const [items, setItems] = useSessionStorage("items", ["a"]); // string[]
// Explicit generic type
interface FormData {
email: string;
message: string;
}
const [form, setForm] = useSessionStorage("form", {
email: "",
message: "",
});
``---
This package maintains comprehensive test coverage to ensure reliability and stability.
📊 View Detailed Coverage Report (GitHub Pages)
Initialization Tests
- Return initial value when sessionStorage is empty
- Return stored value when sessionStorage has data
- Support lazy initialization with function
- Not call initializer when sessionStorage has data
- Fallback to initial value when JSON parse fails
- Call onError when sessionStorage read fails
Same-Tab Sync Tests
- Sync ComponentB when ComponentA updates the same key
- Sync multiple components using the same key
- Sync when using functional updates
- Sync when removeValue is called
- Not affect components with different keys
- Handle rapid updates from different components
- Cleanup listeners on unmount
setValue Tests
- Update value and sessionStorage
- Support functional updates
- Handle object values
- Handle array values
- Call onError when write fails
---
MIT © mirunamu
This package is part of the usefy monorepo.
---
Built with care by the usefy team