Hone logo
Hone
Problems

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 key for the sessionStorage item. If a value already exists in sessionStorage for 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 sessionStorage item and a function to update it. This mimics the API of useState.
  • 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 sessionStorage under the specified key.
  • Update on Storage Changes: The hook should also react to changes made to sessionStorage from other browser tabs or windows (using the storage event). When such a change occurs for the relevant key, 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 sessionStorage and 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:

  1. User opens Tab A and Tab B. Both display "Setting: default".
  2. 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 sessionStorage is 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.stringify and JSON.parse respectively.
  • Error handling for JSON.parse on 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 sessionStorage stores data as strings. You'll need to serialize complex types before storing and deserialize them when retrieving.
  • The storage event 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 sessionStorage for that key. The hook should prioritize the value from sessionStorage.
  • Think about how to handle the case where sessionStorage might 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.
Loading editor...
typescript