← Back to all articles

Engineering / Guide

State Persistence Strategies in React: Custom Hooks vs. Redux-Persist

Comparing state persistence mechanisms in React: building a lightweight useLocalStorage custom hook versus configuring Redux-Persist with security and storage considerations.

Use custom Hook (useLocalStorage)

To persist state we can use local storage to store the data. To implement it, we can create a custom hook called useLocalStorage and we can use it like a useState hook from react. To create that custom hook, we can follow this step:

1. Create a useLocalStorage.js file in our custom hook folder.

2. Add this code;

import React, { useState } from 'react';

export const useLocalStorage = (key, initialValue) => {
  // State to store our value
  // Pass initial state function to useState so logic is only executed once
  const [storedValue, setStoredValue] = useState(() => {
    if (typeof window === "undefined") {
      return initialValue;
    }
    
    try {
      const item = window.localStorage.getItem(key);
      return item ? JSON.parse(item) : initialValue;
    } catch (error) {
      // If error also return initialValue
      return initialValue;
    }
  });

  // Return a wrapped version of useState's setter function that ...
  // ... persists the new value to localStorage.
  const setValue = (value) => {
    try {
     // Allow value to be a function so we have same API as useState
      const valueToStore =
        value instanceof Function ? value(storedValue) : value;
      // Save state
      setStoredValue(valueToStore);
      // Save to local storage
      window.localStorage.setItem(key, JSON.stringify(valueToStore));
    } catch (error) {
      // A more advanced implementation would handle the error case
      console.log(error);
    }
  };

  return [storedValue, setValue];
};

In this example, we define a useLocalStorage hook that takes a key and an initialValue as arguments. The key is used to identify the value in the local storage, and the initialValue is used as the default value if there is no value stored in the local storage for the given `key`.

The useLocalStorage hook returns an array with two values: the current stored value for the given key, and a function to update the stored value.

In the our component, we use the useLocalStorage hook to store and retrieve the value of our state. Whenever the user update the state, it will updates the stored value using the setter function returned by the useLocalStorage hook.

    } catch (error) {
      // A more advanced implementation would handle the error case
      console.log(error);
    }

On this line of code, is used to handle any errors that may occur while setting the value in the local storage. There are several scenarios where an error might occur while setting the value in local storage, such as:

  1. The local storage is full and cannot store any more data.
  2. The user has disabled local storage for the current domain.
  3. There is a security restriction preventing the data from being stored in local storage.

To handle these errors in a more meaningful way, we can modify the catch block to throw an error with a user-friendly message, like this:

} catch (error) {
  // swow a toast to inform the user
  if (
    error instanceof DOMException &&
    error.code === DOMException.QUOTA_EXCEEDED_ERR
  ) {
    console.log('Local storage is full');
  } else if (
    error instanceof DOMException &&
    error.code === DOMException.SECURITY_ERR
  ) {
    console.log(
      'Local storage is disabled or Security restrictions prevent storage in local storage'
    );
  } else {
    console.log(error);
  }
}

For case 2 and 3 was rarely happening. But for case 1, we can handle it like clear the key in our local storage every time we done store the data to the database.

Reference:

Another option: Use Redux-Persist library:

Redux Persist is a library that helps to persist and rehydrate a Redux store across page reloads or even across sessions by storing the state in the browser's storage system (e.g. local storage, session storage, etc.). Here's an example of how to use Redux Persist to persist the state of a Redux store:

  1. Install the required dependencies:
npm install redux redux-persist
  1. Import the necessary modules:
import { createStore } from 'redux';
import { persistStore, persistReducer } from 'redux-persist';
import storage from 'redux-persist/lib/storage'; // defaults to localStorage for web
  1. Define the initial state and reducer for your store:
const initialState = {
  counter: 0,
};

function reducer(state = initialState, action) {
  switch (action.type) {
    case 'INCREMENT':
      return { ...state, counter: state.counter + 1 };
    case 'DECREMENT':
      return { ...state, counter: state.counter - 1 };
    default:
      return state;
  }
}
  1. Define the persist configuration and wrap the reducer with persistReducer:
const persistConfig = {
  key: 'root',
  storage,
};

const persistedReducer = persistReducer(persistConfig, reducer);
  1. Create the store using createStore and the persisted reducer:
const store = createStore(persistedReducer);
  1. Initialize the persistor using persistStore:
const persistor = persistStore(store);
  1. Wrap your app with PersistGate to delay rendering until the persisted state is loaded:
import { PersistGate } from 'redux-persist/integration/react';

function App() {
  return (
    <Provider store={store}>
      <PersistGate loading={null} persistor={persistor}>
        <YourApp />
      </PersistGate>
    </Provider>
  );
}

Now, the state of our Redux store will be persisted across page reloads and across sessions using the browser's storage system. The PersistGate component will delay rendering until the persisted state is loaded, so we don't have to worry about rendering with an empty state. When the persisted state is loaded, it will replace the initial state of your store, so your app will continue with the last state before the reload.

**Reference: **https://www.npmjs.com/package/redux-persist

Storage option, Advantages and Disadvantages:

There are several storage options available for storing data locally in web applications. The most common options are:

1. Local Storage: Local storage is a browser-based storage mechanism that allows you to store key-value pairs in the browser. The data is stored in the browser's cache and persists even after the browser is closed. Local storage has a size limit of around 5-10 MB, depending on the browser.

Advantages of Local Storage:

Disadvantages of Local Storage:

2. Session Storage: Session storage is similar to local storage, but the data is only available during the current browser session. Once the browser is closed, the data is lost.

Advantages of Session Storage:

Disadvantages of Session Storage:

3. Cookies: Cookies are small text files that are stored in the browser's cache and can be used to store key-value pairs. Cookies can be set to expire after a certain period of time, or can be persistent.

Advantages of Cookies:

Disadvantages of Cookies:

4. IndexedDB: IndexedDB is a browser-based database that allows you to store structured data in the browser. It has a larger storage capacity than local storage and session storage, and can be used to store more complex data structures.

Advantages of IndexedDB:

Disadvantages of IndexedDB:

The choice of storage mechanism will depend on the specific needs of your application. If you only need to store small amounts of data, local storage or session storage may be sufficient. If you need to store more complex data structures or larger amounts of data, IndexedDB may be a better

5. Redux: Redux is a state management library for JavaScript applications, and it can be used to store data locally in your web application. Redux works by maintaining a global state object, which can be updated by dispatching actions. Components can then subscribe to changes in the state and update their views accordingly.

Advantages of using Redux for local data storage:

Disadvantages of using Redux for local data storage:

In summary, Redux can be a powerful tool for managing local data in your web application, especially for larger or more complex applications. However, it may not be necessary or appropriate for all applications, and simpler storage mechanisms like local storage or session storage may be more appropriate in some cases.