DEV Community

Cover image for How to use localStorage in React
collegewap
collegewap

Posted on • Originally published at codingdeft.com

How to use localStorage in React

In one of my previous articles, I have explained how to use react context for state management. 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:

npx create-react-app react-local-storage
Enter fullscreen mode Exit fullscreen mode

We will be using BluePrintJS 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:

yarn add @blueprintjs/core
Enter fullscreen mode Exit fullscreen mode

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

@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;
}
Enter fullscreen mode Exit fullscreen mode

Building the App

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

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
Enter fullscreen mode Exit fullscreen mode

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:

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

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

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.

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
Enter fullscreen mode Exit fullscreen mode

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

  • 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

Saving the choices and displaying them

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

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
Enter fullscreen mode Exit fullscreen mode

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

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:

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
Enter fullscreen mode Exit fullscreen mode

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:

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
Enter fullscreen mode Exit fullscreen mode

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 and
view a demo here.

Discussion (0)