A picture of a box, a React logo, and a CopyCat logo.

React localStorage: set and get items with hooks

April 28, 2022
copycat
Uncategorized

The Fastest Way to Build React UI

Convert Figma designs to production-ready React.js code. Build stunning apps and landing pages faster than your peers and competitors.

Introduction

There are two main web storage mechanisms: React localStorage and sessionStorage. These storage types are similar to persistent cookies and session cookies respectively. Local storage and sessionStorage both maintain a separate storage area for each given origin that’s available for the duration of the page session.
There is no difference between local Storage and sessionStorage except for the intended non-persistence of sessionStorage. Local Storage also is used to retrieve data stored from the page session. The storage type only maintains a storage area for the duration of a window’s lifespan in a page session. In other words, changes made on a webpage are only available in a window until it closes.

What is a web storage API?

The web storage API, sometimes known as DOM storage, is a set of mechanisms that enables browsers to store client-side data using key-value pairs.
The key-value pairs are simple stores of storage objects (similar to objects), except they remain persistent ( intact ) during page reloads. The keys and values are always strings, hence, other data type keys are automatically converted to strings.
For example, an integer key value will be converted to a string before being stored in any web storage mechanisms.

const num = 3;
sessionStorage.setItem("Number", JSON.strigify(num));


In this tutorial, we’ll focus on how to use the local Storage storage type with React and hooks.

What is React localStorage?

React local Storage does the same thing as sessionStorage, but it maintains a storage area with no expiry date. That is, data stored with local Storage will persist even after the browser window is closed and reopened.
The most prominent use case of the local Storage mechanism is implementing a dark mode feature. Another popular case of local Storage is for persisting a user’s form input value in a web app. It’s also used as a way to retrieve data from the browser.
We’ll set up a React project with the latter use case: persisting form inputs. We will demonstrate how to use the browser’s local storage with React hooks.

How to use localStorage in React js: Prerequisite

To follow along with this tutorial, you’ll need a fundamental knowledge of React and its concepts, including hooks.
While this is not an introduction to the web storage API, you don’t need prior knowledge to follow along. You will learn the basics of React local Storage and other concepts as we build with it.

Getting started

If you’d like to follow along with the examples used in this article, set up a simple React project with create-react-app by running the command below in your machine’s terminal:

npx create-react-app my-app

Next, open the generated my-app folder with an IDE of your choice. Then, clean up the boilerplate codes in the App.js component. Also clean unnecessary files present in the root folder of the project.
After completing the setup, you can start the development server by running the command below in your terminal:

npm start

Creating a form component 

Now that our project is fully set up and tidied, we can create a form component that we can play around with. First, we create a component folder inside the src folder and then add a form component to it.

component folder for React localstorage

Next, we navigate inside the Form component and add the following:

/src/component/Form.js

import {UseState} from "react"

const Form = () => {
const [userName, setUserName] = useState ("");

  return (
    <form
     onSubmit = {(e) => e.preventDefault()}
    >
      <input
        type="text"
        value={userName}
        onChange={(e) => setName(e.target.value)}
        placeholder="Username"
      />
      
      <label>
        <input
          type="checkbox"
          checked={checked}
        />
        Remember me
      </label>
      <input type="submit" value="Submit"></input>
    </form>
  );
};

export default Form;

The component’s JSX is a simple login form with two inputs and a button. The first input element is of type “text” ( i.e users can input texts into it ), and it accepts a username string value, while the second input element is a checkbox for toggling the “Remember me” option.
We didn’t include a password input field in our form–it is impractical to persist data In a password field.
At this point, if you import the form component into the main App.js component, save the project, and start the development server, you should see something similar to the image below:

React localstorage example

Note:

The purpose of this demo app is to help us understand how local Storage works with React. So styling is not a necessity; we can leave it as is. However, if you feel the need to, you could style it however you want. Cheers!
The code needed to handle the state of the form component is likewise comprehensive:

/src/component/Form.js
….
const [userName, setUserName] = useState ("");
  return (
    ...
      <input
        …
        value = {userName}
        onChange={(e) => setName(e.target.value)}
        ...
      />
       ...
    </form>
  );
};

In the code snippet above:

  • We created a userName state variable with the React useState hook. Then set the initial state ( value ) to an empty string.
  • Next, we add a value attribute to the input element and pass it the userName state variable as a value.
  • Then we add an onChange attribute that invokes a function that sets and updates the input value as the state variable’s value on every keystroke using the setUserName method, as seen in the gif below.
React localstorage gif username example

Right now, our input data won’t persist. if the page refreshes, the input data will be cleared. To persist the input data, so it’s available after every page refresh and even on subsequent revisits to the page, we need to save it in the local Storage.

Saving input value to React localStorage

The web storage API exposes a browser storage object that provides methods for saving, reading, and removing data from the storage API.
setItem() is the primary method for saving data to the storage object. The method saves a key value to a key namein the storage object for subsequent retrieval and usage.

How to store data in local storage in reactjs using Storage.setItem() : storing values

To store our form’s input data in the browser storage, invoke the setItem() method and pass it a key-name and a data item ( key-value ).

localStorage.setItem("key", "value");

In our case, the data item will be the useName state variable:

localStorage.setItem("Username", "userName")

As mentioned earlier, the storage interface only accepts string data types. Values with different data types like arrays or objects must be converted into a JSON string with the JSON.stringify() method:

localStorage.setItem("Username", JSON.stringify(userName));

Here, we converted the value of the userName state variable to a JSON string before saving it in the local Storage.
Since we’ll be saving the input value on each keystroke, we need a way to tell React that our form component needs to do something after it renders. 
We do this by calling the location.setItem() method in the React useEffect hook.

Side effects with UseEffect

The useEffect hook is used for performing side effects in components. In other words, it is used for running some additional codes after React has updated the DOM.
To use the useEffect hook, first, we import it from React like so:

import [ useEffect, React ] from  'react'

The useEffect hook accepts two parameters: a callback function and an optional array of dependencies. The callback function is where we’ll call the localStorage.setItem():

useEffect(() => { 
localStorage.setItem("Username",JSON.stringify(userName))
});

While the second parameter is optional, if we don’t set it, the useEffect hook will run on every render. That means, that every time a state changes in the form component, a re-render happens, which then triggers another effect. We don’t want this.

Prevention

We want to prevent this and run the effect only on the initial render and when a select state changes. So, we add a second dependency parameter to the useEffect and pass it the state or states we want it to depend on. In our case, we want the hook to run every time the value of the userName state variable changes:

useEffect(() => {
….
}, [userName]);

Now, the useEffect hook will only run on the initial component render and whenever the value of the userName state variable changes.
If we go to our browser’s developer tool and navigate to the storage section in the Application tab ( or the storage tab in Firefox ), we’ll see all the browser’s local storage, including our form’s data. We’ll also see how the value of the form’s input element is being saved as a key value of the username key name in the local Storage.

Notice that the initial value of the userName key is an empty string, and whenever the page reloads, the storage is cleared. It returns an empty string, which is the initial value. The reason for this is because the initial state we assigned to the userName state variable is an empty string:

const [userName, setUserName] = useState("");

The state variable doesn’t have access to the saved data in the local Storage. So, on every re-render, it’ll fall back to its initial value, which is an empty string.
We fix this by fetching the saved data from React local Storage and setting it as a default value or initial state of the state variable.

How to get data from localStorage in React js

getItem() is the primary method for fetching or retrieving data from the storage object. Unlike its counterpart, the method returns the key value of a key name or null if the key doesn’t exist in the given storage object. 
In the next section, we’ll use the getItem() method to fetch the saved input data from local Storage and assign it to the state variable.

How to fetch data from api in react using Storage.getItem() : fetching items

To fetch our saved data from the local storage, we need to:

  • Invoke the localStorage.getItem() method and pass it the key name of the data we want to extract as a parameter
  • Parse the JSON string returned by the getItem() method 
  •  Finally, assign the parsed data to the state variable as an initial value

The best way to implement what we’ve laid out above is to create a function in useState that returns the parsed data as the initial state of the useState hook.

const [userName, setUserName] = useState ( () => {
 const savedItem = localStorage.getItem("userName");
const parsedItem = JSON.parse(savedItem);
return parsedItem || "";
});

Here, the function returns a conditional that sets the initial state of the useState hook to the parsed data. If it returns a value or an empty string if it returns null.
Note: JSON.parse() is a method that converts JSON strings to JavaScript objects. Its primary purpose in this context is to convert non-string data types stored in the web storage into objects for easy access.
Both JSON.stringify() and JSON.parse() are optional when working with string values.
Now, if you save the code, the stored input value should appear in the input field. When you reload the page, the data will persist.

As a challenge, try implementing the feature for persisting data on the “Remember me” checkbox element using the same procedure as the input element. 

React localStorage custom hook

If you take on the challenge in the previous section, you’ll create another state logic. Or, you’ll duplicate the previous one to handle the local storage functionality for the “Remember me” checkbox.
While this approach gets the job done, it is adverse. Especially in cases where you have more components that depend on such logic. We can address this snag by creating a React custom hook that’ll share the logic between components that needs it. With this, we wouldn’t have to worry about copying or recreating logic blocks between components anymore.
In the subsequent sections, we’ll learn how to create a React local Storage custom hook to define a single logic for persisting data in multiple components.

How to set data in local storage in React js using React localStorage hook

First, create a new folder inside the src folder and name it hooks. Inside the hooks folder, create a new file, name it useLocalStorage (or anything else you can think of, just ensure to prefix use to it). Then, add the following code:

src/hooks/useLocalStorage.js

const getStorageData = (keyName, defaultValue) =>{
  const savedItem = localStorage.getItem(keyName);
const parsedItem = JSON.parse(savedItem);
return parsedItem || defaultValue;
}

export const useLocalStorage = (keyName, initialValue) => {
  const [value, setValue] = useState(() => {
    return getStorageData(keyName, initialValue);
  });
  
useEffect(() => {
    localStorage.setItem(keyName, JSON.stringify(value));
  }, [keyName, value]);

return [value, setValue];
}

In the code above, first, we imported useState and useEffect hooks from React. Then we created a getStorageData function that encapsulates our local storage logic for fetching saved data from the previous form and accepts two arguments: key and defaultValue

const getStorageData = (keyName, defaultValue) =>{
  const savedItem = localStorage.getItem(keyName);
  const parsedItem = JSON.parse(savedItem);
  return parsedItem || defaultValue;
}

This function fetches saved data from the local storage with a specified key argument and returns a conditional that either exposes the parsed data or a specified defaultValue argument.
In the body of our hook, we created a state variable with the useState hook and return the getStorageData function with the keyName and initialValue parameters from the hook passed into it as arguments:  

src/hooks/useLocalStorage.js

export const useLocalStorage = (keyName, initialValue) => {
  const [value, setValue] = useState(() => {
    return getStorageValue(keyName, initialValue);
  });
  ….

So what we’re doing here is, we’re passing whatever key-name and initial value that is being passed into the useLocalStorage hook from a component to the getStorageData function. This returns either a parsed data from the local Storage or the defaultValue (aka initialValue) as the initial state of the useState hook.
Below the useState is a useEffect block where the localStorage.setItem() method is being called, with the keyName parameter and the value state passed into it, and as dependencies of the useEffect hook.

export const useLocalStorage = (keyName, initialValue) => {
...
useEffect(() => {
    localStorage.setItem(keyName, JSON.stringify(value));
  }, [keyName, value]);

return [value, setValue];
}

Lastly, we return the state variable, value, and the setValue function.
Now we can call the hook from different components in our project and make use of its functionality.

Using localStorage hook

To utilize the local storage hook in our form component, first, we refactor the code by deleting the local Storage logic we created earlier. Then import the hook into the component and call it like so:

src/components/Form

import useLocalStorage from '../hooks/useLocalStorage'

const Form = ()  => {
// Handles localStorage functionality for input //field element

 const [value, setValue] = useLocalStorage("Username", "")
 
// Handles localStorage functionality for //checkbox element

const [checked, setChecked] = useLocalStorage("Checkbox", false)

return(
  {...}
)
}

Here, we declared the useLocalStorage hook twice, one for the input field, and the other for the checkbox. In the first declaration, we destructured userName and setUserName from the hook and pass to it a unique key-name – “Username“, and an empty string as the default value. In the second declaration, we destructured checked, and setChecked, and passed “Checkbox” as the unique key-name and false as the defaulted value.
To get the local storage hook fully functional, we need to do some more refactoring in the component body. Specifically, on the input elements.

src/components/Form

//Input field
<input
        type="text"
        value={userName}
        onChange={(e) => setUserName(e.target.value)}
        placeholder="Username"
      />

//Checkbox
      <label>
        <input
          type="checkbox"
          checked={checked}
        />
        Remember me
      </label>

Here, we pass the userName state variable that we destructured earlier to the value attribute in the input element. Then we allocate the input value of the element to the hook using the setUserName function from the onChange event handler.
We did the same for the checkbox element by passing the checked state variable to the checked attribute and allocating its value to the hook using the setChecked function from the onChange event handler.
Now if you save the project, you should be able to persist the form inputs with local Storage.

In addition, if you want to persist the input fields data only when the the “Remember me” checkbox is checked, all you have to do is add the following piece of code to the input field’s value attribute:      

<input
        ...
        value={checked ? userName : ""}
        ...
 />

Now your input field data will only persist if the checkbox element is checked.

Caveats

local Storage or any client-based storage mechanism is a built-in property of the browser’s window object. So accessing them would mean prefixing window to each property with a dot notation like so:

window.localStorage
window.sessionStorage

But if you look at our code, you’ll notice we ignored the window object when accessing local Storage. But the code runs fine. This is because the window object is a global object, and prefixing it to a storage property is optional.
However, this is not the case with frameworks like Next.js and Remix.js that executes codes on the server-side. The window object is not available on the server-side, so using a storage property like local Storage will throw a “window not defined” error.

SSR error fix

To fix the “window notdefined” error on the server-side, we need to check whether the window object is defined in the current environment before executing the underlying code. This way, our code will only run in environments where the window object is available.
We do this by navigating back to the useLocalStorage.js file and updating the getStorageData function so we have the following:

const getStorageData = (keyName, defaultValue) =>{
  if(typeof window !== "undefined"){
     const savedItem = localStorage.getItem(keyName);
     const parsedItem = JSON.parse(savedItem);
     return parsedItem || defaultValue;
  }
}

What we did in the code above is wrap the logic inside of the getStorageData function with an if statement. The statement checks if the window object is defined or undefined in the current environment.
Note: The local Storage calls inside the useEffect hook will not return an error. The hook only runs on the client-side.

Conclusion

In this article, we’ve covered what localStorage is and how to use it to persist data in the browser using React hooks. We also learned how to create a custom hook to share reusable logic functions between components. For more information, you can read this blog, or you can check out some other how-to’s on React on our blog. Also check out CopyCat, a Figma to React plugin tool that helps you build faster than your competition at a fraction of the time.

Interesting Reads From Our Blog

Related Articles

  • Uncategorized

    The Challenges of Cross-Platform App Development and How to Overcome Them

    Introduction In the world of mobile apps, having a presence on multiple platforms is crucial to reaching a wider audience. However, creating separate apps for each platform can take time, effort, and resource-intensive. That's where cross-platform app development comes in.…

    February 20, 2023
  • Uncategorized

    The Best Methods for Implementing Vertical Align in CSS

    Why CSS Vertical Align is Important https://css-tricks.com/almanac/properties/v/vertical-align/ Have you ever visited a website where the text and images appeared misaligned or unbalanced, making it difficult to read and navigate? This common problem can arise when vertical alignment is incorrectly implemented…

    March 24, 2023

Convert Figma To React

Convert Figma designs to production-ready React.js code. Build stunning apps and landing pages faster than your peers and competitors.

Convert Design to Code