Here is a small React app that is using State and saves updated data to localStorage.
You can create buttons by inputting the name and pressing Add button.
import "./styles.css";
import { useState } from "react";
export default function App() {
const [buttonlist, setButtonList] = useState([]);
const [newName, setNewName] = useState("");
const [storeChecked, setStoreStatus] = useState(false);
// local storage
if (!storeChecked) {
if (localStorage.getItem("ButtonList")) {
console.log("store is available");
let newToDoObject = localStorage.getItem("ButtonList");
console.log(JSON.parse(newToDoObject));
setStoreStatus(true);
setButtonList(JSON.parse(newToDoObject));
} else {
console.log("store is NOT available");
localStorage.setItem("ButtonList", JSON.stringify(buttonlist));
}
} else {
// this update works while rerendering
//localStorage.setItem("ButtonList", JSON.stringify(buttonlist)); // <-- line 23
}
console.log("rerendered buttonlist:", buttonlist);
const ButtonList = ({ button }) => {
return (
<button className="button" value={button.name}>
{button.name}
</button>
);
};
const handleListChange = (event) => {
console.log("target value", event.target.value);
setNewName(event.target.value);
console.log("new name:", newName);
};
const addButtonList = (event) => {
console.log("add to list:", newName);
const buttonListObject = {
name: newName,
id: Math.floor(Math.random(5) * (10000 - 1000)),
tasks: [],
};
event.preventDefault();
setButtonList(buttonlist.concat(buttonListObject));
// this update is always late
localStorage.setItem("ButtonList", JSON.stringify(buttonlist)); // <-- line 52
console.log(buttonlist);
};
return (
<div className="App">
<h2>Button List</h2>
{buttonlist.map((button) => (
<ButtonList key={button.id} button={button} />
))}
<form id="create_list" onSubmit={addButtonList}>
<div>
Name:{" "}
<input
onChange={handleListChange}
value={newName}
id="name_field"
maxLength={60}
/>
</div>
<div>
<button type="submit" className="actionbutton">
Add
</button>
</div>
</form>
</div>
);
}
Now there are two points where the localStorage is updated:
-
addButtonList function, line 52. This is run when the Add button is clicked. It also sets the state of the button list.
-
in the if / else statement, line 23 (commented out)
Updating at point 1. is always late, while point 2. works fine.
I couldn’t get the localStorage visible in Codesandbox but if you refresh the app,
you’ll see that the created buttons are shown, except the last one you created.
So my question is, what is the right way to store the state data in an app like this? Is the point 2. ok? I found an article that suggested using useEffect for handling the up-to-date data.
2
Answers
Quoting from the documentation:
The lifecycle of an Effect
Now you can find yourself that what would be most ideal event to serialise or store states in an App.
mount – definitely no
updates – may be required if your app requires a very high data availability, in the sense, even the latest state update to be available even in the event of an unforeseen and an unfortunate crash of the app.
However unmount may be the most balanced option between performance and data availability. Please be noted that in this case the data will serialise only once – during the time the component is unmounted.
Now when it comes to implementing the same, useEffect is an excellent escape hatch provided by React. As you know, localStorage is not mapped into React. Whenever we encounter something which is not available in React, we need to step out from React. useEffect hook is the way to do that. It solely meant to provide the codes to synchronise two systems – surely one is React and the other one may be a non-React system like localStorage.
Therefore a sample code below would do the job we have been discussing.
Please do note that the empty array given as its dependency means that the code inside the hook will be invoked on mount as well as unmount of the component. It will not be invoked for the state updates.
Either place is correct (or rather they are not incorrect), assuming you understand the React component lifecycle and values you are working with.
Current Implementation:
buttonList
state value in theaddButtonList
callback that prevents it ever seeing any updated state value. ThebuttonList
you update to localStorage is the un-updated current state value.storeChecked
condition. Unintentional side-effects are a React anti-pattern and should be avoided.Suggestions:
Updating localStorage from the callback:
Create a new state value in the callback scope and use that to both enqueue the state update and update localStorage.
Array.concat
creates a new array reference with the argument appended to the source array. Use a lazy initializer function to initialize thebuttonList
state from localStorage.Using a
useEffect
hook to intentionally issue the side-effect to update localStorage:Use a lazy initializer function to initialize the
buttonList
state from localStorage.Between the two options the second is preferred because:
addButtonList
to use a functional state update, e.g. correctly update from the current state value versus whatever valuebuttonlist
might have in the callback function scope.addButtonList
should only update thebuttonList
state by addingto it
useEffect
is specifically for persistingbuttonList
state changes to localStorage. Any other handlers that update the
buttonList
state no longer also need to manually persist it, it’shandled "automagically" by this component and effect.