Hier is an example of a problem I encountered:
import ReactDOM from "react-dom/client";
import React, { useState, useEffect } from "react";
const App = () => {
// problematic
const [radio, setRadio] = useState(1);
useEffect(() => {
const localRadio = localStorage.getItem('radio');
if (localRadio) {
setRadio(+localRadio);
}
}, []);
// My "solution" using an initializer to read from localStorage
// const [radio, setRadio] = useState(() => {
// const localRadio = localStorage.getItem('radio');
// return localRadio ? +localRadio : 1;
// });
useEffect(() => {
localStorage.setItem('radio', radio);
}, [radio]);
const radioChangeHandler = (event) => {
setRadio(+event.target.value);
};
return (
<div>
<h1>useState initializer demo</h1>
<div className="radio-group" onChange={radioChangeHandler}>
<input
type="radio"
value="1"
checked={radio === 1}
id="language1"
name="languageChoice"
/>
<label htmlFor="language1">Javascript</label>
<input
type="radio"
value="2"
checked={radio === 2}
id="language2"
name="languageChoice"
/>
<label htmlFor="language2">HTML</label>
<input
type="radio"
value="3"
checked={radio === 3}
id="language3"
name="languageChoice"
/>
<label htmlFor="language3">CSS</label>
</div>
</div>
);
};
const container = document.querySelector("#root");
const root = ReactDOM.createRoot(container);
root.render(
<React.StrictMode>
<App />
</React.StrictMode>
);
The idea is to write into localStorage everytime radio
changes. And when I open/refresh the site, it should read from the localStorage and set the value. But then I got the problem that the useEffect with [radio]
always overwrites the other one, so I always get 1 as value for radio regardless of what's written in localStorage at the beginning.
(I just found out that the code actually works if I remove StrictMode, I thought I've tested it before... but react wants that the app also works if it renders twice anyway.)
My solution was to use an initializer function to read from localStorage (the commented out code). It works fine, and I was proud of me. Then I read on https://beta.reactjs.org/apis/react/useState#parameters
If you pass a function as initialState, it will be treated as an initializer function. It should be pure, should take no arguments, and should return a value of any type.
Also on https://beta.reactjs.org/apis/react/useReducer#my-reducer-or-initializer-function-runs-twice
Only component, initializer, and reducer functions need to be pure.
Does it mean that I shouldn't read from localStorage (or fetch data from API) in initializer? And what's the right way to do it?