react

How to use localStorage with React

Jun 13th, 2021Abhishek EH3 Min Read

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:

1npx create-react-app react-local-storage

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:

1yarn add @blueprintjs/core

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

index.css
1@import "~normalize.css";
2@import "~@blueprintjs/core/lib/css/blueprint.css";
3@import "~@blueprintjs/icons/lib/css/blueprint-icons.css";
4
5body {
6 margin: 10px auto;
7 max-width: 400px;
8}
9.space {
10 margin: 5px 5px 10px 5px;
11}

Building the App

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

App.js
1import { Button, Card, FormGroup, InputGroup } from "@blueprintjs/core"
2import { useState } from "react"
3
4function App() {
5 const [name, setName] = useState("")
6
7 const formSubmitHandler = e => {
8 e.preventDefault()
9 }
10 return (
11 <div>
12 <Card elevation="1">
13 <form onSubmit={formSubmitHandler}>
14 <FormGroup label="Name" labelFor="name">
15 <InputGroup
16 id="Name"
17 placeholder="Name"
18 type="Name"
19 value={name}
20 onChange={e => setName(e.target.value)}
21 />
22 </FormGroup>
23 <Button intent="primary" text="Submit" fill type="submit" />
24 </form>
25 </Card>
26 </div>
27 )
28}
29
30export 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:

1const formSubmitHandler = e => {
2 e.preventDefault()
3 try {
4 window.localStorage.setItem("user", JSON.stringify({ name, favorites: [] }))
5 } catch (error) {
6 console.log(error)
7 }
8}

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.

App.js
1import {
2 Button,
3 Card,
4 Checkbox,
5 FormGroup,
6 InputGroup,
7} from "@blueprintjs/core"
8import { useEffect, useState } from "react"
9
10const fruits = [
11 "Apple",
12 "Orange",
13 "Guava",
14 "Mango",
15 "Grapes",
16 "Kiwi",
17 "Strawberry",
18]
19
20function App() {
21 const [name, setName] = useState("")
22 const [userData, setUserData] = useState()
23 const [editMode, setEditMode] = useState(false)
24
25 useEffect(() => {
26 // Fetch the user data from the localStorage and set it to the local state userData
27 try {
28 const user = window.localStorage.getItem("user")
29
30 if (!user) {
31 setUserData(null)
32 } else {
33 const parsedData = JSON.parse(user)
34 setUserData(parsedData)
35 if (parsedData.favorites.length === 0) {
36 setEditMode(true)
37 }
38 }
39 } catch (error) {
40 console.log(error)
41 setUserData(null)
42 }
43 }, [])
44
45 const onFruitChecked = (e, fruit) => {
46 // Check if the fruit exists in the current list of favorites
47 const index = userData.favorites.indexOf(fruit)
48 // If the checkbox is checked and fruit is not part of favorites
49 if (e.target.checked && index === -1) {
50 setUserData(prevValues => {
51 // Add the fruit to the current list of favorites
52 return { ...prevValues, favorites: [...prevValues.favorites, fruit] }
53 })
54 } else if (!e.target.checked && index !== -1) {
55 // If the checkbox is unchecked and fruit is part of favorites
56 setUserData(prevValues => {
57 // Remove the fruit from the current list of favorites
58 return {
59 ...prevValues,
60 favorites: [
61 ...prevValues.favorites.slice(0, index),
62 ...prevValues.favorites.slice(index + 1),
63 ],
64 }
65 })
66 }
67 }
68
69 const formSubmitHandler = e => {
70 e.preventDefault()
71 try {
72 setUserData({ name, favorites: [] })
73 setEditMode(true)
74 window.localStorage.setItem(
75 "user",
76 JSON.stringify({ name, favorites: [] })
77 )
78 } catch (error) {
79 console.log(error)
80 }
81 }
82
83 return (
84 <div>
85 {userData === null && (
86 <Card elevation="1">
87 <form onSubmit={formSubmitHandler}>
88 <FormGroup label="Name" labelFor="name">
89 <InputGroup
90 id="Name"
91 placeholder="Name"
92 type="Name"
93 value={name}
94 onChange={e => setName(e.target.value)}
95 />
96 </FormGroup>
97 <Button intent="primary" text="Submit" fill type="submit" />
98 </form>
99 </Card>
100 )}
101 {userData && editMode && (
102 <Card elevation="1">
103 <p>
104 Welcome <strong>{userData.name}</strong>, choose your favorite
105 fruits:
106 </p>
107 {fruits.map(fruit => {
108 return (
109 <Checkbox
110 key={fruit}
111 label={fruit}
112 inline={true}
113 className="space"
114 checked={userData.favorites.indexOf(fruit) !== -1}
115 onChange={e => {
116 onFruitChecked(e, fruit)
117 }}
118 />
119 )
120 })}
121 <Button intent="primary" text="Save" fill type="submit" />
122 </Card>
123 )}
124 </div>
125 )
126}
127
128export 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

  • 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.

App.js
1import {
2 Button,
3 Card,
4 Checkbox,
5 FormGroup,
6 InputGroup,
7 Tag,
8} from "@blueprintjs/core"
9import { useEffect, useState } from "react"
10
11const fruits = [
12 "Apple",
13 "Orange",
14 "Guava",
15 "Mango",
16 "Grapes",
17 "Kiwi",
18 "Strawberry",
19]
20
21function App() {
22 const [name, setName] = useState("")
23 const [userData, setUserData] = useState()
24 const [editMode, setEditMode] = useState(false)
25
26 useEffect(() => {
27 // Fetch the user data from the localStorage and set it to the local state userData
28 try {
29 const user = window.localStorage.getItem("user")
30
31 if (!user) {
32 setUserData(null)
33 } else {
34 const parsedData = JSON.parse(user)
35 setUserData(parsedData)
36 if (parsedData.favorites.length === 0) {
37 setEditMode(true)
38 }
39 }
40 } catch (error) {
41 console.log(error)
42 setUserData(null)
43 }
44 }, [])
45
46 const onFruitChecked = (e, fruit) => {
47 // Check if the fruit exists in the current list of favorites
48 const index = userData.favorites.indexOf(fruit)
49 // If the checkbox is checked and fruit is not part of favorites
50 if (e.target.checked && index === -1) {
51 setUserData(prevValues => {
52 // Add the fruit to the current list of favorites
53 return { ...prevValues, favorites: [...prevValues.favorites, fruit] }
54 })
55 } else if (!e.target.checked && index !== -1) {
56 // If the checkbox is unchecked and fruit is part of favorites
57 setUserData(prevValues => {
58 // Remove the fruit from the current list of favorites
59 return {
60 ...prevValues,
61 favorites: [
62 ...prevValues.favorites.slice(0, index),
63 ...prevValues.favorites.slice(index + 1),
64 ],
65 }
66 })
67 }
68 }
69
70 const formSubmitHandler = e => {
71 e.preventDefault()
72 try {
73 setUserData({ name, favorites: [] })
74 setEditMode(true)
75 window.localStorage.setItem(
76 "user",
77 JSON.stringify({ name, favorites: [] })
78 )
79 } catch (error) {
80 console.log(error)
81 }
82 }
83
84 const saveFavorites = () => {
85 try {
86 window.localStorage.setItem("user", JSON.stringify(userData))
87 setEditMode(false)
88 } catch (error) {
89 console.log(error)
90 }
91 }
92
93 return (
94 <div>
95 {userData === null && (
96 <Card elevation="1">
97 <form onSubmit={formSubmitHandler}>
98 <FormGroup label="Name" labelFor="name">
99 <InputGroup
100 id="Name"
101 placeholder="Name"
102 type="Name"
103 value={name}
104 onChange={e => setName(e.target.value)}
105 />
106 </FormGroup>
107 <Button intent="primary" text="Submit" fill type="submit" />
108 </form>
109 </Card>
110 )}
111 {userData &&
112 (editMode ? (
113 <Card elevation="1">
114 <p>
115 Welcome <strong>{userData.name}</strong>, choose your favorite
116 fruits:
117 </p>
118 {fruits.map(fruit => {
119 return (
120 <Checkbox
121 key={fruit}
122 label={fruit}
123 inline={true}
124 className="space"
125 checked={userData.favorites.indexOf(fruit) !== -1}
126 onChange={e => {
127 onFruitChecked(e, fruit)
128 }}
129 />
130 )
131 })}
132 <Button
133 intent="primary"
134 text="Save"
135 fill
136 type="submit"
137 onClick={saveFavorites}
138 />
139 </Card>
140 ) : (
141 <Card elevation="1">
142 <p>
143 Welcome <strong>{userData.name}</strong>, your favorite fruits
144 are:
145 </p>
146 {userData.favorites.map(fruit => {
147 return (
148 <Tag
149 key={fruit}
150 round
151 minimal
152 large
153 intent="success"
154 className="space"
155 >
156 {fruit}
157 </Tag>
158 )
159 })}
160 <Button
161 intent="primary"
162 text="Change"
163 fill
164 type="submit"
165 onClick={() => setEditMode(true)}
166 />
167 </Card>
168 ))}
169 </div>
170 )
171}
172
173export 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

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:

useLocalStorage.js
1import { useState } from "react"
2
3const useLocalStorage = (key, initialValue) => {
4 const [state, setState] = useState(() => {
5 // Initialize the state
6 try {
7 const value = window.localStorage.getItem(key)
8 // Check if the local storage already has any values,
9 // otherwise initialize it with the passed initialValue
10 return value ? JSON.parse(value) : initialValue
11 } catch (error) {
12 console.log(error)
13 }
14 })
15
16 const setValue = value => {
17 try {
18 // If the passed value is a callback function,
19 // then call it with the existing state.
20 const valueToStore = value instanceof Function ? value(state) : value
21 window.localStorage.setItem(key, JSON.stringify(valueToStore))
22 setState(value)
23 } catch (error) {
24 console.log(error)
25 }
26 }
27
28 return [state, setValue]
29}
30
31export 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:

App.js
1import {
2 Button,
3 Card,
4 Checkbox,
5 FormGroup,
6 InputGroup,
7 Tag,
8} from "@blueprintjs/core"
9import { useState } from "react"
10import useLocalStorage from "./hooks/useLocalStorage"
11
12const fruits = [
13 "Apple",
14 "Orange",
15 "Guava",
16 "Mango",
17 "Grapes",
18 "Kiwi",
19 "Strawberry",
20]
21
22function App() {
23 const [name, setName] = useState("")
24 const [userData, setUserData] = useLocalStorage("user", null)
25 // Set edit mode to true whenever the userData is not present or
26 // selected favorites are 0
27 const [editMode, setEditMode] = useState(
28 userData === null || userData?.favorites?.length === 0
29 )
30
31 const onFruitChecked = (e, fruit) => {
32 // Check if the fruit exists in the current list of favorites
33 const index = userData.favorites.indexOf(fruit)
34 // If the checkbox is checked and fruit is not part of favorites
35 if (e.target.checked && index === -1) {
36 setUserData(prevValues => {
37 // Add the fruit to the current list of favorites
38 return { ...prevValues, favorites: [...prevValues.favorites, fruit] }
39 })
40 } else if (!e.target.checked && index !== -1) {
41 // If the checkbox is unchecked and fruit is part of favorites
42 setUserData(prevValues => {
43 // Remove the fruit from the current list of favorites
44 return {
45 ...prevValues,
46 favorites: [
47 ...prevValues.favorites.slice(0, index),
48 ...prevValues.favorites.slice(index + 1),
49 ],
50 }
51 })
52 }
53 }
54
55 const formSubmitHandler = e => {
56 e.preventDefault()
57 try {
58 setUserData({ name, favorites: [] })
59 setEditMode(true)
60 } catch (error) {
61 console.log(error)
62 }
63 }
64
65 return (
66 <div>
67 {userData === null && (
68 <Card elevation="1">
69 <form onSubmit={formSubmitHandler}>
70 <FormGroup label="Name" labelFor="name">
71 <InputGroup
72 id="Name"
73 placeholder="Name"
74 type="Name"
75 value={name}
76 onChange={e => setName(e.target.value)}
77 />
78 </FormGroup>
79 <Button intent="primary" text="Submit" fill type="submit" />
80 </form>
81 </Card>
82 )}
83 {userData &&
84 (editMode ? (
85 <Card elevation="1">
86 <p>
87 Welcome <strong>{userData.name}</strong>, choose your favorite
88 fruits:
89 </p>
90 {fruits.map(fruit => {
91 return (
92 <Checkbox
93 key={fruit}
94 label={fruit}
95 inline={true}
96 className="space"
97 checked={userData.favorites.indexOf(fruit) !== -1}
98 onChange={e => {
99 onFruitChecked(e, fruit)
100 }}
101 />
102 )
103 })}
104 <Button
105 intent="primary"
106 text="Done"
107 fill
108 type="submit"
109 onClick={() => setEditMode(false)}
110 />
111 </Card>
112 ) : (
113 <Card elevation="1">
114 <p>
115 Welcome <strong>{userData.name}</strong>, your favorite fruits
116 are:
117 </p>
118 {userData.favorites.map(fruit => {
119 return (
120 <Tag
121 key={fruit}
122 round
123 minimal
124 large
125 intent="success"
126 className="space"
127 >
128 {fruit}
129 </Tag>
130 )
131 })}
132 <Button
133 intent="primary"
134 text="Change"
135 fill
136 type="submit"
137 onClick={() => setEditMode(true)}
138 />
139 </Card>
140 ))}
141 </div>
142 )
143}
144
145export 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 and view a demo here.

Leave a Comment

© 2021 CodingDeft.Com