Follow

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use
Contact

Why does Map updated twice with setTimeout and React in Strict Mode

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

MEDevel.com: Open-source for Healthcare and Education

Collecting and validating open-source software for healthcare, education, enterprise, development, medical imaging, medical records, and digital pathology.

Visit Medevel

<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?

duplicated

>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>
Add a comment

Leave a Reply

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use

Discover more from Dev solutions

Subscribe now to keep reading and get access to the full archive.

Continue reading