I have following React component:
App.tsx:
function App() {
const [countdownTimers, setCountdownTimers] = React.useState<
Map<number, number>
>(new Map([[1, 60]]));
useEffect(() => {
const timeoutId = setInterval(() => {
setCountdownTimers((prevState) => {
console.log(prevState);
for (const [timerKey, timer] of prevState) {
prevState.set(timerKey, timer - 1);
}
return new Map(prevState);
});
}, 1000);
return () => {
clearInterval(timeoutId);
};
}, []);
return <>{countdownTimers.get(1)}</>;
};
index.tsx
<React.StrictMode>
<App />
</React.StrictMode>
Code above is expected to subtract 1 from all values in Map every second. But due to StrictMode it subtracts 2. Removing <React.StrictMode> solves issue, but I want to understand why StrictMode behave this way only with Map
Could you please advise why it’s this way?
>Solution :
In strict mode, state updater functions are invoked twice in an attempt to detect possible bugs.
Your code here does have an arguable bug – you’re mutating the existing state in the Map here:
setCountdownTimers((prevState) => {
console.log(prevState);
for (const [timerKey, timer] of prevState) {
prevState.set(timerKey, timer - 1);
}
return new Map(prevState);
});
Although you create a new Map when returning, you’re still calling prevState.set – mutating it. This means that the second time the (strict) state updater runs, the Map it sees (in prevState the second time) has already had its values decremented once.
Instead of mutating the existing Map, create the new Map immediately, and only change that new Map.
function App() {
const [countdownTimers, setCountdownTimers] = React.useState(new Map([[1, 60]]));
React.useEffect(() => {
const timeoutId = setInterval(() => {
setCountdownTimers((prevState) => {
const newMap = new Map(prevState);
console.log(JSON.stringify([...newMap.entries()]));
for (const [timerKey, timer] of prevState) {
newMap.set(timerKey, timer - 1);
}
return newMap;
});
}, 1000);
return () => {
clearInterval(timeoutId);
};
}, []);
return countdownTimers.get(1);
};
ReactDOM.createRoot(document.querySelector('.react')).render(<React.StrictMode><App /></React.StrictMode>);
<script crossorigin src="https://unpkg.com/react@18/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
<div class='react'></div>
