Using Local Storage in JavaScript and React

Learn the simplest way to persist values in the browser using the local storage API.

· 3 min read
Using Local Storage in JavaScript and React

Local Storage in JavaScript

In the browser, you can access local storage through the window.localStorage property. The window is assumed though, so you can omit it and simply write localStorage.

Local storage gives access to 4 important functions:

  • setItem(key, value) - assign a string value to a key in local storage.
  • getItem(key) - get the value assigned to a key.
  • removeItem(key) - removes the value by key.
  • clear() - removes all values in local storage.

Here's the usage example:

const value = { name: 'Vincas', surname: 'Stonys' };

// 1️⃣ Stores the value. Must convert value to string first
localStorage.setItem('userInfo', JSON.stringify(value));

// 2️⃣ Gets the stored value and convert it to original value type
JSON.parse(localStorage.getItem('userInfo'));
Example usage of local storage in JavaScript.

What is local storage?

Local storage is basically a simple key-value map in your browser. It has a simple synchronous API that's available in all modern browsers.

The local storage is separate for every origin (hostname and port): https://vistontea.com and https://blog.vistontea.com won't share local storage.

You can inspect values stored in local storage via developer tools. For instance, using CTRL+SHIFT+C keyboard shortcut in Chrome (or CMD+SHIFT+C on Mac), then opening the Application tab.

Local storage in chrome devtools.

Local Storage in React

In React, you can use local storage directly using plain JavaScript.

A common use case for local storage is to persist local component state. In that case, it's useful to write a custom hook that synchronizes component state to local storage.

Here's a custom useStoredState hook example 👇

import { SetStateAction, useState } from 'react';

function useStoredState<T>(key: string, defaultValue?: T | (() => T)) {
  // 👇 Load stored state into regular react component state
  const [state, setState] = useState<T>(() => {
    const storedState = localStorage.getItem(key);

    if (storedState) {
      // 🚩 Data is stored as string so need to parse
      return JSON.parse(storedState) as T;
    }

    // No stored state - load default value.
    // It could be a function initializer or plain value.
    return defaultValue instanceof Function ? defaultValue() : defaultValue;
  });

  // 👇 Keeps the exact same interface as setState - value or setter function.
  const setValue = (value: SetStateAction<T>) => {
    const valueToStore = value instanceof Function ? value(state) : value;
    localStorage.setItem(key, JSON.stringify(valueToStore));
    setState(valueToStore);
  };

  // as const tells TypeScript you want tuple type, not array.
  return [state, setValue] as const;
}
Custom hook to sync local storage with the component state.
  1. The hook keeps the same return type as useState - a tuple with the value and value setter. Its parameters are the local storage key and the default value or the initializer function.
  2. It initializes the local component state by getting its value by key from local storage and parsing it in the state initializer function.
  3. A custom state setter function wraps the native setState with additional logic to store the value in local storage using localStorage.setItem function.

Here's how you might use it:

import * as React from 'react';
import useStoredState from './useStoredState';

export default function App() {
  // 👇 Will be stored in local storage, but used as regular state
  const [items, setItems] = useStoredState('items', ['item1', 'item2']);

  const handleAddClick = () => {
    setItems((items) => [...items, `item${items.length + 1}`]);
  };

  return (
    <div>
      <ul>
        {items.map((item) => (
          <li key={item}>{item}</li>
        ))}
      </ul>
      <button onClick={handleAddClick}>Add</button>
    </div>
  );
}

You can see the full code example with demo here: https://stackblitz.com/edit/react-ts-x4nwoj?file=App.tsx

This is how it looks like in action 👇

0:00
/

Using Local Storage in TypeScript

You will notice that the localStorage property doesn't have great typings. It's useful to create your own wrapper around local storage that handles automatic value serialization and parsing.

Here's an example 👇

const storage = {
  set: (key: string, value: any) => {
    localStorage.setItem(key, JSON.stringify(value));
  },
  get: <T>(key: string, defaultValue?: T): T => {
    const value = localStorage.getItem(key);
    return (value ? JSON.parse(value) : defaultValue) as T;
  },
  remove: (key: string) => {
    localStorage.removeItem(key);
  },
};

export default storage;
TypeScript wrapper for local storage with typings.

You could now use the storage utility instead of localStorage and provide the type for the value stored in local storage:

// No need to parse the value, it's handled by the wrapper
const value = storage.get<{ name: string, surname: string }>('userInfo');

Conclusion

Local storage is the easiest way to persist value in the browser, often eliminating the need for the backend server in the case of frontend-only apps.

In JavaScript, it's as simple as using functions exposed by localStorage object. In React you may use the same API, however, it's helpful to be more declarative and create higher-level utilities. I've given an example of a custom hook that synchronizes component state to local storage.