Using localStorage with React Hooks
Tags: react, javascript, webdev
Published: 2021-08-11 19:33:19

**Written by [Ibadehin Mojeed] (https://blog.logrocket.com/author/ibadehinmojeed/)** ✏️

`localStorage` is one of the two mechanisms of a browsers’ web storage. It allows users to save data as key/value pairs in the browser for later use.

Unlike the `sessionStorage` mechanism, which persists data in the browser storage as long as the current browser tab is running, `localStorage` does not clear data when the browser closes.

This makes it ideal for persisting data not bound to the current browser tab.

Developers often implement `localStorage` when adding a dark mode feature to an application, persisting a to-do item, or persisting a user’s form input values, among many other use cases.

In this guide, we cover how to use `localStorage` to persist a user’s form input in the browser storage using React Hooks. We’ll also cover how to create a custom React Hook to share similar logic between multiple components.

## `localStorage` with React Hooks prerequisites

To follow this guide, ensure you have a basic [understanding of React](http://ibaslogic.com/react-tutorial-for-beginners/) and [React Hooks](https://blog.logrocket.com/react-hooks-cheat-sheet-unlock-solutions-to-common-problems-af4caf699e70/). Also, ensure you have [Node.js installed](https://nodejs.org) on your computer.

## Initial `localStorage` project setup

Working with a fresh React application, let’s head over to the computer terminal and run the following command to create a new React project:

```bash
npx create-react-app localstorage-react-hook
```

Once the project folder generates, open it with a code editor and start the development server by running the `npm start` command.

The project should launch in the browser at [http://localhost:3000/](http://localhost:3000/).

## Creating a React form component

As mentioned earlier, we will use the `localStorage` to persist a user’s form input in the browser storage.

Like every React application, our focus is on the `src` folder. So, let’s delete all the files inside the `src` and create an `index.js` file inside `src` to avoid a frontend break.

Then, add the following code to `index.js`:

```javascript
import React from "react";
import ReactDOM from "react-dom";

import App from "./components/App";
// styles
import "./app.css";

ReactDOM.render(
  <React.StrictMode>
    <App />
  </React.StrictMode>,
  document.getElementById("root")
);
```

Notice that we imported a CSS file to add styling to the app. So, let’s create an `app.css` folder in the `src` folder.

[Copy the styles from the localstorage-react-hook-project](https://github.com/Ibaslogic/localstorage-react-hook-project/blob/main/src/App.css) and add them to the `app.css` file.

Next, create a `components` folder in the `src` folder to hold the component files. Then, add an `App.js` file and a `Form1.js` file. The `App.js` file is the root and parent component while `Form1.js` will hold the form inputs.

Add the following code in the `components/App.js` file:

```javascript
import Form1 from "./Form1";

const App = () => {
  return (
    <div className="container">
      <h1>localStorage with React hooks</h1>
      <Form1 />
    </div>
  );
};
export default App;
```

And finally, add this code to the `components/Form1.js` file:

```javascript
import { useState } from "react";

const Form1 = () => {
  const [name, setName] = useState("");

  return (
    <form>
      <input
        type="text"
        value={name}
        onChange={(e) => setName(e.target.value)}
        placeholder="Full name"
        aria-label="fullname"
      />
      <input type="submit" value="Submit"></input>
    </form>
  );
};

export default Form1;
```

After saving the files, test the project and you should see this rendering:

![UI Shows Form and Button With Code Below Showing Component Rendering](https://blog.logrocket.com/wp-content/uploads/2021/08/React-form-component-rendered.gif)

The code above is the simplest implementation of the form inputs in React. By using the `useState` React Hook to control the component, we keep the input state up-to-date on every keystroke, as seen above.

But, once we trigger a page refresh, the input data clears, which is expected.

To persist the input data so it’s available on a page reload or on subsequent revisits, we must save the data in the `localStorage`.

## Saving the form input data in `localStorage`

[`localStorage` gives us access to a browser’s `Storage` object](https://blog.logrocket.com/localstorage-javascript-complete-guide/). The `Storage` object has methods available to save, read, and remove data, among many other actions.

To see a list of the `Storage` methods, open the browser console and type `localStorage`. After pressing enter, the methods become available on the `Storage` object's `prototype`.

### Using the `setItem()` method

To store the form input data in the browser storage, we must invoke the `setItem()` storage method by using the following syntax:

```javascript
localStorage.setItem("key", "value")
```

The browser storage only accepts data-type strings. So, for values of different data types like the object or array, we must convert it to a JSON string using `JSON.stringify()`.

### Using the `useEffect` Hook to perform side effects

We can also use the `useEffect` React Hook to [perform side effects](https://reactjs.org/docs/hooks-effect.html), such as storing data in the browser storage. This makes this Hook a perfect place to call the `setItem` method.

Open the `components/Form1.js` file and add the following code above the `return` statement:

```javascript
useEffect(() => {
  // storing input name
  localStorage.setItem("name", JSON.stringify(name));
}, [name]);
```

Ensure to [import the `useEffect` from React](https://blog.logrocket.com/guide-to-react-useeffect-hook/) like so:

```javascript
import { useState, useEffect } from "react";
```

Here, we’ve assigned a key, `"name"`, and a dynamic value from the state variable, which is `name`.

The initial value of the `name` state variable defaults to an empty string:

```javascript
const [name, setName] = useState("");
```

Using `JSON.stringify` in the `setItem` is optional when saving string data to the storage:

```javascript
localStorage.setItem("name", JSON.stringify(name));
```

However, `JSON.stringify` is required if the value is a different data type, like an object or array.

Now, save the file and test the project; we should see the following render:

![useEffect Rendered In Browser With Code Below](https://blog.logrocket.com/wp-content/uploads/2021/08/useEffect-rendered.gif)

On every keystroke, the input value saves in the local storage because the `useEffect` Hook holding the `setItem` storage method runs on the first component render and after every state change.

However, on a page reload, the value in the storage returns to an empty string. This is happening because we’ve assigned a default empty string to the state variable, `name`. Hence, React uses the empty value on the initial render.

Now, instead of assigning an empty string, we must get the updated state value at every point from the storage and assign it as the default state value.

## Reading data from the `localStorage`

On an initial page load, instead of assigning an empty string to the `name` state variable, we must assign a function that accesses the local storage, retrieve the saved value, and use that value as the default.

### Using the `getItem()` method

Update the `useState` Hook in the `components/Form1.js` file:

```javascript
const [name, setName] = useState(() => {
  // getting stored value
  const saved = localStorage.getItem("name");
  const initialValue = JSON.parse(saved);
  return initialValue || "";
});
```

Here, we use the `getItem()` storage method to retrieve data from the local storage. The `JSON.parse()` used in the code deserializes the returned JSON string from the storage.

Both the `JSON.Stringify` and the `JSON.parse` are optional when working with string values (as seen in our case). However, other data types, like objects and arrays, require them.

Save the file and test the project. The input data should be available in the form field on a page reload or a later page visit.

![Input Remains On Page Refresh](https://blog.logrocket.com/wp-content/uploads/2021/08/Input-remains-page-refresh.gif)

## Creating a custom React Hook to persist form inputs

Sometimes we might want to render and persist more form inputs, such as a text input and a checkbox input, in a different component.

While we could easily copy the logic from the one we’ve already created and use it in the new component, it’s not always practicable, especially if we decide to create more of these inputs.

Instead, React allows us to extract and [share similar logic between components using custom Hooks](https://blog.logrocket.com/advanced-react-hooks-creating-custom-reusable-hooks/).

In this section, we will learn how to create a custom Hook to persist form inputs in multiple components.

Let’s start by creating another form. In the `src/components` folder, create a new file called `Form2.js`, and add the following code:

```javascript
import { useState } from "react";

const Form2 = () => {
  const [name, setName] = useState("");
  const [checked, setChecked] = useState(false);

  return (
    <form>
      <input
        type="text"
        value={name}
        onChange={(e) => setName(e.target.value)}
        placeholder="Full name"
        aria-label="fullname"
      />
      <label>
        <input
          type="checkbox"
          checked={checked}
          onChange={(e) => setChecked(e.target.checked)}
        />{" "}
        Not a robot?
      </label>
      <input type="submit" value="Submit"></input>
    </form>
  );
};

export default Form2;
```

Then, import and use the component in the `components/App.js` file:

```javascript
// ...
import Form2 from "./Form2";

const App = () => {
  return (
    <div className="container">
      {/* ... */}
      <Form2 />
    </div>
  );
};
export default App;
```

Save the files and view the form in the frontend.

![Input Field With Checkbox In Form](https://blog.logrocket.com/wp-content/uploads/2021/08/input-field-checkbox.png)

Interacting with this form does not persist the state value in `localStorage` since we don’t have the logic yet.

So, let’s define a single logic to manage all our form inputs.

### Extracting the `localStorage` logic

To begin extracting the `localStorage` logic, create a file called `useLocalStorage.js` in the `src` folder and add the following code:

```javascript
import { useState, useEffect } from "react";

function getStorageValue(key, defaultValue) {
  // getting stored value
  const saved = localStorage.getItem(key);
  const initial = JSON.parse(saved);
  return initial || defaultValue;
}

export const useLocalStorage = (key, defaultValue) => {
  const [value, setValue] = useState(() => {
    return getStorageValue(key, defaultValue);
  });

  useEffect(() => {
    // storing input name
    localStorage.setItem(key, JSON.stringify(value));
  }, [key, value]);

  return [value, setValue];
};
```

Taking a closer look at the code above, we’ve only extracted the storage logic from the `components/Form1.js` file. We’ve done nothing special.

By creating a custom Hook called `useLocalStorage`, we maintain all of the storage logic we have in the `Form1` component.

The `useLocalStorage` Hook expects two arguments: the `key` and the `defaultValue`. This means we expect to pass these values when calling the Hook in our different components.

Note that you can name your custom Hook anything, but ensure you start with `use`.

### Using the `useLocalStorage` custom Hook

In the `components/Form1.js` file, replace the logic above the `return` statement with the custom Hook so you have the following:

```javascript
import { useLocalStorage } from "../useLocalStorage";

const Form1 = () => {
  const [name, setName] = useLocalStorage("name", "");

  return (
    <form>
      {/* ... */}
    </form>
  );
};

export default Form1;
```

After importing the custom Hook, we can use it and pass along the unique key and the default value, which, in this case, is an empty string.

If we do the same for the `Form2` component in the `components/Form2js` file, we should have the following:

```javascript
import { useLocalStorage } from "../useLocalStorage";

const Form2 = () => {
  const [name, setName] = useLocalStorage("name2", "");
  const [checked, setChecked] = useLocalStorage("checked", false);

  return (
    <form>
      {/* ... */}
    </form>
  );
};

export default Form2;
```

Save all the files and test the project. We should be able to persist all the form inputs in `localStorage`.

![Side-By-Side Render and Code Showing Persisting Form Inputs With localStorage](https://blog.logrocket.com/wp-content/uploads/2021/08/persist-form-inputs-localstorage.gif)

Good job!

## Problems accessing `localStorage` for a server-side rendered application

When working with a framework like Next.js that executes code on the server-side, using `localStorage` gets an error stating, “window is not defined.”

The `localStorage` as used in our code is a built-in property of the `window` object, `window.localStorage`.

In our code, we ignored the `window` while accessing the `localStorage` because it’s a global object; we can choose to include the `window` object because it’s optional.

Now, this `window` object is not available on the server side but rather the client side/browser, which prompts the error.

To fix the error on the server side, check whether the `window` object is defined or not. This way, our code only runs on the environment where the `window` is available.

Open the `src/useLocalStorage.js` file and update the `getStorageValue()` function so you have the following:

```javascript
function getStorageValue(key, defaultValue) {
  // getting stored value
  if (typeof window !== "undefined") {
    const saved = localStorage.getItem(key);
    const initial = saved !== null ? JSON.parse(saved) : defaultValue;
    return initial;
  }
}
```

Don’t forget that we’ve also used the `localStorage` inside the `useEffect` Hook in the `useLocalStorage.js` file.

But in this case, the `localStorage` is safe because the `useEffect` Hook only runs on the client-side where we have access to the `window` object.

Test the project to ensure everything still works as expected.

## Conclusion

We’ve covered how to use the `localStorage` to persist data in a browser using the React Hooks. We’ve also learned how to create a custom Hook to extract component logic into reusable functions.

If you enjoyed this guide, share it around the web. And, if you have any questions or contributions, please share them via the comment section.

Find the [entire source code for the project here](https://github.com/Ibaslogic/localstorage-react-hook-project).

---

## Full visibility into production React apps

Debugging React applications can be difficult, especially when users experience issues that are hard to reproduce. If you’re interested in monitoring and tracking Redux state, automatically surfacing JavaScript errors, and tracking slow network requests and component load time, [try LogRocket] (https://www2.logrocket.com/react-performance-monitoring).

[![Alt Text](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/6v4gw3tgq2topvafgpgz.png)](https://www2.logrocket.com/react-performance-monitoring)[![LogRocket Dashboard Free Trial Banner] (https://blog.logrocket.com/wp-content/uploads/2017/03/1d0cd-1s_rmyo6nbrasp-xtvbaxfg.png)](https://www2.logrocket.com/react-performance-monitoring)

[LogRocket] (https://www2.logrocket.com/react-performance-monitoring) is like a DVR for web apps, recording literally everything that happens on your React app. Instead of guessing why problems happen, you can aggregate and report on what state your application was in when an issue occurred. LogRocket also monitors your app's performance, reporting with metrics like client CPU load, client memory usage, and more.

The LogRocket Redux middleware package adds an extra layer of visibility into your user sessions. LogRocket logs all actions and state from your Redux stores.

Modernize how you debug your React apps — [start monitoring for free] (https://www2.logrocket.com/react-performance-monitoring).