2

I am working on my project and i noticed that when strict mode is turned on it pushes two identical elements into my array. When the strict mode is off it pushes only one element into the array. Is there any explanation why this happens?

import {getRandomNumber} from './utils';


export function registerTicket() {
    const newTicket = {
        number: getRandomNumber(),
        color: 'red',
    }
    this.setState((prevState) => {
        prevState.tickets.push(newTicket);
        return {
            remainingTickets: --prevState.remainingTickets,
            tickets: prevState.tickets,

        }
    })
}

Here is my state.

    this.state = {
      winningNumber: getRandomNumber(),
      remainingTickets: 5,
      tickets: [],
      finished: false
    };
Bergi
  • 630,263
  • 148
  • 957
  • 1,375
  • Check may help you out https://stackoverflow.com/questions/53183362/what-is-strictmode-in-react – Nithin - Techidiots Jul 21 '21 at 07:25
  • 1
    Are you looking for [other answers on SO](https://stackoverflow.com/questions/61053432/react-usestate-cause-double-rendering) or something from the [official docs](https://reactjs.org/docs/strict-mode.html)? Long story short it helps expose unintentional side-effects by double-invoking certain lifecycle methods and React hooks. In your case you are mutating the state object. – Drew Reese Jul 21 '21 at 07:28
  • Where and how are you calling `registerTicket`? And what is `this`, are you working with a class component? – Bergi Jul 15 '23 at 14:13

2 Answers2

5

React StrictMode helps expose unintentional side-effects by intentionally double-invoking certain lifecycle methods and React hooks. The setState updater function is one that is called twice. I'll leave the in-depth explanation to the linked questions.

Detecting unexpected side-effects

Your unintentional side-effect is a state mutation of the previous tickets state when you push a new element into it and when you pre-decrement the remainingTickets state. The hat-trick is that you also don't return a new tickets array referece.

this.setState((prevState) => {
  prevState.tickets.push(newTicket); // <-- mutation!!
  return {
    remainingTickets: --prevState.remainingTickets, // <-- mutation!!
    tickets: prevState.tickets, // <-- same old array reference
  }
})

The solution is to shallow copy the previous state array into a new array reference and append the new element.

this.setState((prevState) => ({
  remainingTickets: prevState.remainingTickets - 1,
  tickets: [...prevState.tickets, newTicket],
}));
Drew Reese
  • 165,259
  • 14
  • 153
  • 181
  • oh so the setstate is called twice ? – ProHunter67 Jul 21 '21 at 08:06
  • 1
    @ProHunter67 The callback function passed to `setState` is invoked twice, but essentially, yes, that is the effect. – Drew Reese Jul 21 '21 at 08:08
  • Yes one more thing. Why when we create a new array reference it adds the element only once when the function is invoked twice? – ProHunter67 Jul 21 '21 at 08:12
  • 1
    @ProHunter67 Because you aren't mutating anything. In both invokations you are applying the same update to the same previous state, the result is identical each time. – Drew Reese Jul 21 '21 at 08:13
  • okey i think i understood. the second time that the function is invoked the state is changed ? – ProHunter67 Jul 21 '21 at 12:43
  • 1
    @ProHunter67 Yes. Think of it more like React just calling the updater function that computes the next state (*that's what it is really*) twice but only storing the result for the next render once. If there are zero side-effects in the function, then you see the same result as if it had been called only once. ***BUT*** if there are side-effects and/or mutations, these can leak out and be exposed. The first call did the `array.push` and mutated previous state, and then a second call did another `array.push` and mutated it again, and then on the next render you see the result of both pushes. – Drew Reese Jul 21 '21 at 15:40
  • Ok but one more thing. I did mutation to the prevState and i get the same result as the function was invoked once `this.setState((prevState) => { prevState.tickets.splice(index, 1); return ({ remainingTickets: prevState.remainingTickets + 1, tickets: prevState.tickets.slice() }); })` I am mutating the previous state but i am still getting one element removed. However i am coppying the array in the return i just can't understand why when my return is `prevState.tickets` i get two removes and when it is with slice only one. – ProHunter67 Jul 23 '21 at 22:19
  • @ProHunter67 Without creating a running demo to test this out my guess is it is because you return a *new* array reference from `.slice`, so the second invocation might not "see" the mutation. In other words, in spite of mutating the previous state, it's masked by the new array reference. I see this all the time where an element in the previous state array is mutated, but because it's then spread into a new array, the mutation is "hidden". The mutation isn't exposed because the state is replaced. Let me create a codesandbox to see if I can confirm my suspicions. – Drew Reese Jul 24 '21 at 06:51
  • i tested this out and it works. But i just can't understand why the second invokation doesn't see the mutation. Why when i create a new array reference the mutation is "hidden". – ProHunter67 Jul 24 '21 at 06:56
  • 1
    @ProHunter67 Yeah, confirmed the behavior [here](https://codesandbox.io/s/twilight-feather-d57kw?file=/src/App.js). TBH I think the answer you seek lies in the ReactDOM and reconciliation code for how it handles state updater functions and StrictMode. Most of my knowledge is from the docs and empirical experience. – Drew Reese Jul 24 '21 at 07:11
  • Yes thank you, it is very confusing how this thing works. There are a lot of things on the low level that happen, that we don't need to understand how they work. – ProHunter67 Jul 24 '21 at 10:41
0

React.StrictMode causes the render function to be called twice in development, to identify unintended side-effects

Priyanka Panjabi
  • 403
  • 5
  • 12