DEV Community

Cover image for React Custom Hook: useStorage
Sergey Leschev
Sergey Leschev

Posted on

React Custom Hook: useStorage

In this article series, we embark on a journey through the realm of custom React hooks, discovering their immense potential for elevating your development projects. Our focus today is on the "useStorage" hook, one of the many carefully crafted hooks available in the collection of React custom hooks.

Github: https://github.com/sergeyleschev/react-custom-hooks

import { useCallback, useState, useEffect } from "react"

export function useLocalStorage(key, defaultValue) {
    return useStorage(key, defaultValue, window.localStorage)
}
export function useSessionStorage(key, defaultValue) {
    return useStorage(key, defaultValue, window.sessionStorage)
}
function useStorage(key, defaultValue, storageObject) {
    const [value, setValue] = useState(() => {
        const jsonValue = storageObject.getItem(key)
        if (jsonValue != null) return JSON.parse(jsonValue)
        if (typeof defaultValue === "function") {
            return defaultValue()
        } else {
            return defaultValue
        }
    })
    useEffect(() => {
        if (value === undefined) return storageObject.removeItem(key)
        storageObject.setItem(key, JSON.stringify(value))
    }, [key, value, storageObject])
    const remove = useCallback(() => {
        setValue(undefined)
    }, [])
    return [value, setValue, remove]
}
Enter fullscreen mode Exit fullscreen mode

The useStorage hook provides two convenient functions: useLocalStorage and useSessionStorage. With useLocalStorage, you can effortlessly store and retrieve data in the browser's local storage, while useSessionStorage offers the same functionality but with the session storage instead.

One of the key advantages of this custom hook is its simplicity. You can use it to store any type of data, such as strings, numbers, or even complex objects, with just a few lines of code. Additionally, useStorage handles the serialization and deserialization of data for you, so you don't have to worry about converting values to and from JSON.

Another advantage is the automatic synchronization between the stored data and the component's state. Whenever the stored data changes, the hook updates the component's state accordingly. Similarly, when the component's state changes, the hook automatically persists the new value to the storage. This bidirectional synchronization ensures that your application always reflects the latest data, making it ideal for scenarios where real-time updates are crucial.

The useStorage hook also provides a remove function, allowing you to easily delete stored values when they are no longer needed. This functionality comes in handy when implementing features like logout buttons or clearing user-specific data.

import { useSessionStorage, useLocalStorage } from "./useStorage"

export default function StorageComponent() {
    const [name, setName, removeName] = useSessionStorage("name", "Sergey")
    const [age, setAge, removeAge] = useLocalStorage("age", 26)
    return (
        <div>
            <div>
                {name} - {age}
            </div>
            <button onClick={() => setName("John")}>Set Name</button>
            <button onClick={() => setAge(40)}>Set Age</button>
            <button onClick={removeName}>Remove Name</button>
            <button onClick={removeAge}>Remove Age</button>
        </div>
    )
}
Enter fullscreen mode Exit fullscreen mode

You can use the useStorage hook in a variety of scenarios. For example, imagine you have a settings panel where users can customize their preferences. By using useLocalStorage, you can easily store and retrieve these settings, ensuring that they persist across page reloads or even if the user closes and reopens the browser.

Full Version | React Custom Hooks:
https://dev.to/sergeyleschev/supercharge-your-react-projects-with-custom-hooks-pl4

Top comments (2)

Collapse
 
oculus42 profile image
Samuel Rouse

As a possible addition, there is a StorageEvent API to notify if storage changes in another document. This can be important if your application is loaded in multiple tabs.

I haven't tested it but an additional useEffect should support that capability.

useEffect(() => {
    const storageEventCheck = (event) => {
      if (event.key !== key) return;
      if (event.storageArea !== storageObject) return;
      if (event.newValue === value) return;
      setValue(event.newValue);
    };
    window.addEventListener('storage', storageEventCheck);
    return () => window.removeEventListener('storage', storageEventCheck);
  }, [value, setValue]);
Enter fullscreen mode Exit fullscreen mode
Collapse
 
oculus42 profile image
Samuel Rouse

This is great! I really like the design, leveraging useState to perform the initial get, and useEffect to update storage, which avoids unnecessary updates to the storage interface if setValue is called multiple times with the same value.

A couple of caveats when using browser storage:

  • Storage has limited capacity of a few MB and throws QUOTA_EXCEEDED_ERR: DOM Exception 22 if it is full. This is not usually a big issue, but some third-party scripts use a lot of sessionStorage.
  • Storage can be disabled. It is disabled by default in Private Browsing mode of Safari on macOS and iOS.
  • Not specific to storage, but both JSON.parse and JSON.stringify can throw errors.