Implement useSessionStorage React Hook
Create a custom React hook called useSessionStorage that allows components to easily read from and write to the browser's sessionStorage. This hook should provide a reactive way to manage session storage data, similar to how useState manages component state.
Problem Description
The goal is to implement a reusable React hook named useSessionStorage that abstracts away the complexities of interacting with the browser's sessionStorage API. This hook should:
- Initialize State: Accept an initial value and a
keyfor thesessionStorageitem. If a value already exists insessionStoragefor the given key, it should be used as the initial state. Otherwise, the provided initial value should be used. - Provide State and Updater: Return an array containing the current value of the
sessionStorageitem and a function to update it. This mimics the API ofuseState. - Persist to Session Storage: Whenever the state is updated via the provided updater function, the new value should be automatically serialized (e.g., to JSON) and saved into
sessionStorageunder the specifiedkey. - Update on Storage Changes: The hook should also react to changes made to
sessionStoragefrom other browser tabs or windows (using thestorageevent). When such a change occurs for the relevantkey, the hook's state should update accordingly. - Handle Serialization/Deserialization: The hook should handle the serialization of complex data types (like objects and arrays) into strings before storing them in
sessionStorageand deserialization back into their original types when retrieved.
Examples
Example 1: Basic Usage
import React, { useState } from 'react';
import useSessionStorage from './useSessionStorage'; // Assuming hook is in this file
function CounterComponent() {
// Key: 'myCounter', Initial value: 0
const [count, setCount] = useSessionStorage<number>('myCounter', 0);
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>Increment</button>
<button onClick={() => setCount(count - 1)}>Decrement</button>
</div>
);
}
Input: User clicks "Increment" button twice.
Output: The displayed "Count" will change from 0 to 1, then to 2. The value 2 will also be stored in sessionStorage under the key 'myCounter'. If the component is unmounted and remounted, or the page is refreshed, the count will still be 2 (unless it's cleared from sessionStorage manually).
Example 2: Storing Objects
import React from 'react';
import useSessionStorage from './useSessionStorage';
interface User {
id: number;
name: string;
}
function UserProfile() {
const initialUser: User = { id: 1, name: 'Alice' };
const [user, setUser] = useSessionStorage<User>('currentUser', initialUser);
const changeName = () => {
setUser({ ...user, name: 'Bob' });
};
return (
<div>
<p>User ID: {user.id}</p>
<p>User Name: {user.name}</p>
<button onClick={changeName}>Change Name to Bob</button>
</div>
);
}
Input: The component mounts. The user clicks "Change Name to Bob".
Output: The displayed "User Name" will change from "Alice" to "Bob". The entire user object { id: 1, name: 'Bob' } will be stored in sessionStorage under the key 'currentUser', serialized as a JSON string.
Example 3: Reacting to External Changes
Imagine two tabs open to the same application. Tab A:
import useSessionStorage from './useSessionStorage';
function TabA() {
const [setting, setSetting] = useSessionStorage<string>('appSetting', 'default');
return (
<div>
<p>Setting in Tab A: {setting}</p>
<button onClick={() => setSetting('updated_from_A')}>Update Setting from A</button>
</div>
);
}
Tab B:
import useSessionStorage from './useSessionStorage';
function TabB() {
const [setting, setSetting] = useSessionStorage<string>('appSetting', 'default');
return (
<div>
<p>Setting in Tab B: {setting}</p>
<button onClick={() => setSetting('updated_from_B')}>Update Setting from B</button>
</div>
);
}
Input:
- User opens Tab A and Tab B. Both display "Setting: default".
- User clicks "Update Setting from A" in Tab A. Output:
- Tab A's "Setting" updates to "updated_from_A".
- Tab B's "Setting" also updates to "updated_from_A" automatically (without user interaction in Tab B).
Constraints
- The hook must be implemented in TypeScript.
- The hook should be generic to support any serializable data type (e.g.,
string,number,boolean,object,Array). - The hook should gracefully handle cases where
sessionStorageis not available (e.g., in environments where it's disabled or not supported), falling back to in-memory state. - Serialization and deserialization should use
JSON.stringifyandJSON.parserespectively. - Error handling for
JSON.parseon corrupted data is not strictly required for this challenge, but be mindful of it. - The hook should be efficient and avoid unnecessary re-renders.
Notes
- Remember that
sessionStoragestores data as strings. You'll need to serialize complex types before storing and deserialize them when retrieving. - The
storageevent listener is crucial for synchronizing state across multiple tabs/windows. Make sure to clean up this listener when the component unmounts to prevent memory leaks. - Consider what happens if the user provides an initial value, but a value already exists in
sessionStoragefor that key. The hook should prioritize the value fromsessionStorage. - Think about how to handle the case where
sessionStoragemight be full or throw an error when attempting to set an item. For this challenge, you can log such errors and potentially fall back to in-memory state.