How to use localStorage in React
Published: 2021-08-09 07:46:49

In one of my previous articles, I have explained [how to use react context for state management](https://www.codingdeft.com/posts/react-context/). In this tutorial, we will see how to access local storage and use it to store the application state.

We will be building an application to input the name of the user,
then we will ask them to choose their favorite fruits and save them in the local storage.
When the user visits the page next time, we will display their favorite fruits and will provide them with an option to change them.

# Setting up the project

First, let's create a react app using the following command:

```bash
npx create-react-app react-local-storage
```

We will be using [BluePrintJS](https://blueprintjs.com/) to style the application
so that we don't have to worry about the styling part and we can focus on the logic.

Run the following command to install BluePrintJS:

```bash
yarn add @blueprintjs/core
```

Now, let's import the stylesheet files related to BluePrintJS in `index.css` and we will add some basic styling:

```css
@import "~normalize.css";
@import "~@blueprintjs/core/lib/css/blueprint.css";
@import "~@blueprintjs/icons/lib/css/blueprint-icons.css";

body {
  margin: 10px auto;
  max-width: 400px;
}
.space {
  margin: 5px 5px 10px 5px;
}
```

# Building the App

In App.js let's create a form with input box to enter name and a submit button:

```jsx
import { Button, Card, FormGroup, InputGroup } from "@blueprintjs/core"
import { useState } from "react"

function App() {
  const [name, setName] = useState("")

  const formSubmitHandler = e => {
    e.preventDefault()
  }
  return (
    <div>
      <Card elevation="1">
        <form onSubmit={formSubmitHandler}>
          <FormGroup label="Name" labelFor="name">
            <InputGroup
              id="Name"
              placeholder="Name"
              type="Name"
              value={name}
              onChange={e => setName(e.target.value)}
            />
          </FormGroup>
          <Button intent="primary" text="Submit" fill type="submit" />
        </form>
      </Card>
    </div>
  )
}

export default App
```

Here we are storing the name in the local storage and we are not doing anything when the form is submitted.

Let's save the name to the local storage in the `formSubmitHandler` function:

```js
const formSubmitHandler = e => {
  e.preventDefault()
  try {
    window.localStorage.setItem("user", JSON.stringify({ name, favorites: [] }))
  } catch (error) {
    console.log(error)
  }
}
```

Here,

- We are converting the Javascript object to string since local storage can store only string values.
- We are also setting an empty array named `favorites`, which will be used later to store user's favorite fruits.
- We have enclosed the code within a try-catch block since accessing localStorage may cause an exception
  if the localStorage is not supported or the user has blocked access to it.

If you run the application and submit the form, you will be able to see the name getting saved in the localStorage:

![saved name](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/fvvpwlspv569wly7csie.png)

## Displaying the options

Since we have saved the name to the localStorage, let's have a list of fruits and display it to the user.

```jsx
import {
  Button,
  Card,
  Checkbox,
  FormGroup,
  InputGroup,
} from "@blueprintjs/core"
import { useEffect, useState } from "react"

const fruits = [
  "Apple",
  "Orange",
  "Guava",
  "Mango",
  "Grapes",
  "Kiwi",
  "Strawberry",
]

function App() {
  const [name, setName] = useState("")
  const [userData, setUserData] = useState()
  const [editMode, setEditMode] = useState(false)

  useEffect(() => {
    // Fetch the user data from the localStorage and set it to the local state userData
    try {
      const user = window.localStorage.getItem("user")

      if (!user) {
        setUserData(null)
      } else {
        const parsedData = JSON.parse(user)
        setUserData(parsedData)
        if (parsedData.favorites.length === 0) {
          setEditMode(true)
        }
      }
    } catch (error) {
      console.log(error)
      setUserData(null)
    }
  }, [])

  const onFruitChecked = (e, fruit) => {
    // Check if the fruit exists in the current list of favorites
    const index = userData.favorites.indexOf(fruit)
    // If the checkbox is checked and fruit is not part of favorites
    if (e.target.checked && index === -1) {
      setUserData(prevValues => {
        // Add the fruit to the current list of favorites
        return { ...prevValues, favorites: [...prevValues.favorites, fruit] }
      })
    } else if (!e.target.checked && index !== -1) {
      // If the checkbox is unchecked and fruit is part of favorites
      setUserData(prevValues => {
        // Remove the fruit from the current list of favorites
        return {
          ...prevValues,
          favorites: [
            ...prevValues.favorites.slice(0, index),
            ...prevValues.favorites.slice(index + 1),
          ],
        }
      })
    }
  }

  const formSubmitHandler = e => {
    e.preventDefault()
    try {
      setUserData({ name, favorites: [] })
      setEditMode(true)
      window.localStorage.setItem(
        "user",
        JSON.stringify({ name, favorites: [] })
      )
    } catch (error) {
      console.log(error)
    }
  }

  return (
    <div>
      {userData === null && (
        <Card elevation="1">
          <form onSubmit={formSubmitHandler}>
            <FormGroup label="Name" labelFor="name">
              <InputGroup
                id="Name"
                placeholder="Name"
                type="Name"
                value={name}
                onChange={e => setName(e.target.value)}
              />
            </FormGroup>
            <Button intent="primary" text="Submit" fill type="submit" />
          </form>
        </Card>
      )}
      {userData && editMode && (
        <Card elevation="1">
          <p>
            Welcome <strong>{userData.name}</strong>, choose your favorite
            fruits:
          </p>
          {fruits.map(fruit => {
            return (
              <Checkbox
                key={fruit}
                label={fruit}
                inline={true}
                className="space"
                checked={userData.favorites.indexOf(fruit) !== -1}
                onChange={e => {
                  onFruitChecked(e, fruit)
                }}
              />
            )
          })}
          <Button intent="primary" text="Save" fill type="submit" />
        </Card>
      )}
    </div>
  )
}

export default App
```

In the above code,

- We have an effect where we fetch the user data from the local storage, pretty much similar to how we stored it.
  Since the data is stored in string format, we are converting it back to Javascript object.

  > If you want to learn more about useEffect,
  > I have written a comprehensive article on [how to use useEffect in React](https://www.codingdeft.com/posts/react-useeffect-hook/)

- We have introduced 2 additional local states,
  one to store the user data and another to store a boolean value called `editMode`,
  which will be used later to toggle between the display and edit screens.
- In the `formSubmitHandler` function,
  we are updating the user data and `editMode` so that the user will be switched to edit mode once they submit their name.
- While rendering the application, we are checking if the `userData` is `null`, i.e., if user is visiting the page for the first time,
  then show them the form to submit the name otherwise allow them to choose their favorite fruits.
- We have a function called `onFruitChecked`, which updates the currently selected favorite fruits when the user checks or un-checks them.

Now if you run the application, it will present you with options as shown below:

![edit screen](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/hl706dt2x0jshibei3q6.png)

## Saving the choices and displaying them

Now we have the user-selected choices in `userData.favorites` array. Let's save it to the localStorage.

```jsx
import {
  Button,
  Card,
  Checkbox,
  FormGroup,
  InputGroup,
  Tag,
} from "@blueprintjs/core"
import { useEffect, useState } from "react"

const fruits = [
  "Apple",
  "Orange",
  "Guava",
  "Mango",
  "Grapes",
  "Kiwi",
  "Strawberry",
]

function App() {
  const [name, setName] = useState("")
  const [userData, setUserData] = useState()
  const [editMode, setEditMode] = useState(false)

  useEffect(() => {
    // Fetch the user data from the localStorage and set it to the local state userData
    try {
      const user = window.localStorage.getItem("user")

      if (!user) {
        setUserData(null)
      } else {
        const parsedData = JSON.parse(user)
        setUserData(parsedData)
        if (parsedData.favorites.length === 0) {
          setEditMode(true)
        }
      }
    } catch (error) {
      console.log(error)
      setUserData(null)
    }
  }, [])

  const onFruitChecked = (e, fruit) => {
    // Check if the fruit exists in the current list of favorites
    const index = userData.favorites.indexOf(fruit)
    // If the checkbox is checked and fruit is not part of favorites
    if (e.target.checked && index === -1) {
      setUserData(prevValues => {
        // Add the fruit to the current list of favorites
        return { ...prevValues, favorites: [...prevValues.favorites, fruit] }
      })
    } else if (!e.target.checked && index !== -1) {
      // If the checkbox is unchecked and fruit is part of favorites
      setUserData(prevValues => {
        // Remove the fruit from the current list of favorites
        return {
          ...prevValues,
          favorites: [
            ...prevValues.favorites.slice(0, index),
            ...prevValues.favorites.slice(index + 1),
          ],
        }
      })
    }
  }

  const formSubmitHandler = e => {
    e.preventDefault()
    try {
      setUserData({ name, favorites: [] })
      setEditMode(true)
      window.localStorage.setItem(
        "user",
        JSON.stringify({ name, favorites: [] })
      )
    } catch (error) {
      console.log(error)
    }
  }

  const saveFavorites = () => {
    try {
      window.localStorage.setItem("user", JSON.stringify(userData))
      setEditMode(false)
    } catch (error) {
      console.log(error)
    }
  }

  return (
    <div>
      {userData === null && (
        <Card elevation="1">
          <form onSubmit={formSubmitHandler}>
            <FormGroup label="Name" labelFor="name">
              <InputGroup
                id="Name"
                placeholder="Name"
                type="Name"
                value={name}
                onChange={e => setName(e.target.value)}
              />
            </FormGroup>
            <Button intent="primary" text="Submit" fill type="submit" />
          </form>
        </Card>
      )}
      {userData &&
        (editMode ? (
          <Card elevation="1">
            <p>
              Welcome <strong>{userData.name}</strong>, choose your favorite
              fruits:
            </p>
            {fruits.map(fruit => {
              return (
                <Checkbox
                  key={fruit}
                  label={fruit}
                  inline={true}
                  className="space"
                  checked={userData.favorites.indexOf(fruit) !== -1}
                  onChange={e => {
                    onFruitChecked(e, fruit)
                  }}
                />
              )
            })}
            <Button
              intent="primary"
              text="Save"
              fill
              type="submit"
              onClick={saveFavorites}
            />
          </Card>
        ) : (
          <Card elevation="1">
            <p>
              Welcome <strong>{userData.name}</strong>, your favorite fruits
              are:
            </p>
            {userData.favorites.map(fruit => {
              return (
                <Tag
                  key={fruit}
                  round
                  minimal
                  large
                  intent="success"
                  className="space"
                >
                  {fruit}
                </Tag>
              )
            })}
            <Button
              intent="primary"
              text="Change"
              fill
              type="submit"
              onClick={() => setEditMode(true)}
            />
          </Card>
        ))}
    </div>
  )
}

export default App
```

In the above code,

- We have added a function called `saveFavorites`, which saves the favorites to the localStorage and sets the `editMode` to `false`.
- We are displaying the favorite fruits inside nice little tags.
- We have also given an option to go back to edit mode, to update the favorites.

If we run the application now, you will see the favorite fruits getting saved in the localStorage.

![saved favorites](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/zh5247uyigxgkbmnf640.png)

If you refresh the page, you will see the data is persisted.

# Creating useLocalStorage hook

You might have observed that we are accessing the local storage in multiple places.
Let's create a hook so that we can separate it into a file and it can act as a utility function.

Create a folder named `hooks` and a file called `useLocalStorage.js` inside it with the following code:

```js
import { useState } from "react"

const useLocalStorage = (key, initialValue) => {
  const [state, setState] = useState(() => {
    // Initialize the state
    try {
      const value = window.localStorage.getItem(key)
      // Check if the local storage already has any values,
      // otherwise initialize it with the passed initialValue
      return value ? JSON.parse(value) : initialValue
    } catch (error) {
      console.log(error)
    }
  })

  const setValue = value => {
    try {
      // If the passed value is a callback function,
      //  then call it with the existing state.
      const valueToStore = value instanceof Function ? value(state) : value
      window.localStorage.setItem(key, JSON.stringify(valueToStore))
      setState(value)
    } catch (error) {
      console.log(error)
    }
  }

  return [state, setValue]
}

export default useLocalStorage
```

In the above hook,

- We have a local state to store the localStorage data, which has a initialize function,
  which checks if a value corresponding to the passed key exists.
  If it exists, then it initializes the state with the data from the local storage.
  Otherwise, it sets the value to the initial value, which is passed to the hook.
- We have `setValue` function, which checks if the passed value is a callback function.
  If it is a callback function, then it calls it with the existing state and updates the response of the callback to the state and localStorage.
- Finally, we return both the `state` as well as the `setValue`, similar to that of a `useState` hook.

Let's now use the newly created hook in `App.js`:

```jsx
import {
  Button,
  Card,
  Checkbox,
  FormGroup,
  InputGroup,
  Tag,
} from "@blueprintjs/core"
import { useState } from "react"
import useLocalStorage from "./hooks/useLocalStorage"

const fruits = [
  "Apple",
  "Orange",
  "Guava",
  "Mango",
  "Grapes",
  "Kiwi",
  "Strawberry",
]

function App() {
  const [name, setName] = useState("")
  const [userData, setUserData] = useLocalStorage("user", null)
  // Set edit mode to true whenever the userData is not present or
  // selected favorites are 0
  const [editMode, setEditMode] = useState(
    userData === null || userData?.favorites?.length === 0
  )

  const onFruitChecked = (e, fruit) => {
    // Check if the fruit exists in the current list of favorites
    const index = userData.favorites.indexOf(fruit)
    // If the checkbox is checked and fruit is not part of favorites
    if (e.target.checked && index === -1) {
      setUserData(prevValues => {
        // Add the fruit to the current list of favorites
        return { ...prevValues, favorites: [...prevValues.favorites, fruit] }
      })
    } else if (!e.target.checked && index !== -1) {
      // If the checkbox is unchecked and fruit is part of favorites
      setUserData(prevValues => {
        // Remove the fruit from the current list of favorites
        return {
          ...prevValues,
          favorites: [
            ...prevValues.favorites.slice(0, index),
            ...prevValues.favorites.slice(index + 1),
          ],
        }
      })
    }
  }

  const formSubmitHandler = e => {
    e.preventDefault()
    try {
      setUserData({ name, favorites: [] })
      setEditMode(true)
    } catch (error) {
      console.log(error)
    }
  }

  return (
    <div>
      {userData === null && (
        <Card elevation="1">
          <form onSubmit={formSubmitHandler}>
            <FormGroup label="Name" labelFor="name">
              <InputGroup
                id="Name"
                placeholder="Name"
                type="Name"
                value={name}
                onChange={e => setName(e.target.value)}
              />
            </FormGroup>
            <Button intent="primary" text="Submit" fill type="submit" />
          </form>
        </Card>
      )}
      {userData &&
        (editMode ? (
          <Card elevation="1">
            <p>
              Welcome <strong>{userData.name}</strong>, choose your favorite
              fruits:
            </p>
            {fruits.map(fruit => {
              return (
                <Checkbox
                  key={fruit}
                  label={fruit}
                  inline={true}
                  className="space"
                  checked={userData.favorites.indexOf(fruit) !== -1}
                  onChange={e => {
                    onFruitChecked(e, fruit)
                  }}
                />
              )
            })}
            <Button
              intent="primary"
              text="Done"
              fill
              type="submit"
              onClick={() => setEditMode(false)}
            />
          </Card>
        ) : (
          <Card elevation="1">
            <p>
              Welcome <strong>{userData.name}</strong>, your favorite fruits
              are:
            </p>
            {userData.favorites.map(fruit => {
              return (
                <Tag
                  key={fruit}
                  round
                  minimal
                  large
                  intent="success"
                  className="space"
                >
                  {fruit}
                </Tag>
              )
            })}
            <Button
              intent="primary"
              text="Change"
              fill
              type="submit"
              onClick={() => setEditMode(true)}
            />
          </Card>
        ))}
    </div>
  )
}

export default App
```

As you may see, we are using the `useLocalStorage` in a similar way we use `useState` hook and we got rid of the `useEffect` and `saveFavorites` (since checking on the checkbox itself saves it to the localStorage) functions.

# Demo and Source Code

You can download the source code [here](https://github.com/collegewap/react-localstorage) and
view a demo [here](https://react-localstorage.vercel.app/).