Syncing Entire State to Local Storage in React
May 2, 2020 • 7 Minute Read
Introduction
Local Storage is a Web API that allows applications to store data in the user's browser. The stored data can be used in future sessions with no expiration date. The data stored in the browser will continue to persist indefinitely unless the user clears browser data or you explicitly add a line of code that clears the storage.
You can take advantage of Local Storage in your React application by storing the state and persisting the state across page refreshes and new sessions.
Local Storage API
Local Storage is available in the global window object, window.localStorage. Data is stored in Local Storage as key-value pairs. The keys and values are always a string, so even if you pass an integer or object, it will be automatically converted to a string.
Following are the methods that can be used to access and update Local Storage.
- setItem(): Add or update stored data by passing a key.
- getItem(): Retrieve the data by key.
- removeItem(): Remove the data by key.
- key(): Get the nth key in the Storage by passing a number.
- clear(): Clear all the data.
Using Local Storage with React
Using Local Storage in your React application is pretty straightforward. Since the localStorage object is available globally, it can be accessed from any React component.
Take a look at the example below.
import React, { Component } from 'react';
class App extends React.Component {
state = {
name: localStorage.getItem('name') || "";
}
componentDidUpdate(prevProps, prevState) {
if(prevState.name !== this.state.name)
localStorage.setItem("name", this.state.name);
}
render() {
<input
value={this.state.name}
onChange={e => this.setState({ name: e.target.value })}
/>
}
}
export default App;
By default, load the state from the Local Storage, and whenever the state changes, i.e. inside componentDidUpdate(), check if the previous state is not equal to the current state. If they are not equal, that means the state has changed, and you need to update that in the Local Storage.
But the above setup will only work for a single component; you surely don't want to repeat yourself by accessing localStorage across multiple components and worry about how many keys you have stored.
A better pattern would be to create a higher-order component that will access localStorage.
Local Storage Higher-Order Component
A higher-order component (HOC) is an advanced pattern in React that dictates reusing component logic. In this case, you want to abstract the Local Storage API so that it can be reused multiple times. Also, this way, your component doesn't have to know how the data is stored or how it is retrieved.
First, create a component that will use the higher-order component.
import React, { Component } from "react";
import "./App.css";
import withLocalStorage from "./withLocalStorage";
class App extends Component {
constructor(props) {
super(props);
this.state = { name: "", password: "", cpassword: "", ...props.data };
}
handleInputChange = (name, value) => {
this.setState({ [name]: value });
};
componentDidUpdate(prevProps) {
if (prevProps.data !== this.state) this.props.setData(this.state);
}
render() {
return (
<div className="App">
<div>
<input
onChange={e =>
this.handleInputChange(e.target.name, e.target.value)
}
value={this.state.name}
name="name"
placeholder="Name"
/>
</div>
<div>
<input
onChange={e =>
this.handleInputChange(e.target.name, e.target.value)
}
value={this.state.password}
name="password"
placeholder="Password"
type="password"
/>
</div>
<div>
<input
onChange={e =>
this.handleInputChange(e.target.name, e.target.value)
}
value={this.state.cpassword}
name="cpassword"
placeholder="Confirm Password"
type="password"
/>
</div>
<button type="button">Create Account</button>
</div>
);
}
}
export default withLocalStorage(App);
withLocalStorage is the HOC, which you'll create next.
import React, { Component } from "react";
const withLocalStorage = ChildComponent => {};
export default withLocalStorage;
Inside the the withLocalStorage() function, first check if localStorage is available or not. Depending on the availability, return the child component or return another component that has access to the localStorage methods.
const withLocalStorage = ChildComponent => {
if (!localStorage) return ChildComponent;
class LocalStorageComponent extends Component {}
return LocalStorageComponent;
};
To determine the key for storing the state, consider the child component's name. Note that components can have the same name across your codebase, so make sure you handle that in a real-world project.
const withLocalStorage = ChildComponent => {
if (!localStorage) return ChildComponent;
let name = ChildComponent.name;
class LocalStorageComponent extends Component {
state = {
data: JSON.parse(localStorage.getItem(name)) || {}
};
render() {
return <ChildComponent />;
}
}
return LocalStorageComponent;
};
By default, the state of this component will be derived from Local Storage.
Next, create a method that will set the Local Storage data and it along with the data as props to <ChildComponent />.
const withLocalStorage = ChildComponent => {
if (!localStorage) return ChildComponent;
let name = ChildComponent.name;
class LocalStorageComponent extends Component {
state = {
data: JSON.parse(localStorage.getItem(name)) || {}
};
setData = data => {
this.setState({ data });
localStorage.setItem(name, JSON.stringify(data));
};
render() {
return <ChildComponent data={this.state.data} setData={this.setData} />;
}
}
return LocalStorageComponent;
};
Once you have the higher-order component ready, it time to test it. Go ahead and check out the results.
If you fill the input fields and reload the page, you'll notice that the value of the fields does not get cleared.
Conclusion
In this guide, you learned how to sync the state of your component with Local Storage. Syncing state with Local Storage will help you persist the state in the browser. This is helpful when you're asking a user to fill out massive forms. If the user accidentally reloads the page or closes the window, the data doesn't get lost, which improves the overall user experience with your application. Be sure to follow best practices and avoid repeating yourself while writing the code.
If you face issues with your code, feel free to contact me at CodeAlphabet.