Do you want to request a feature or report a bug? Bug
What is the current behavior?
When wrapping a with React.StrictMode
, setState
is fired twice
If the current behavior is a bug, please provide the steps to reproduce and if possible a minimal demo of the problem. Your bug will get fixed much faster if we can run your code and it doesn't have dependencies other than React. Paste the link to your JSFiddle (https://jsfiddle.net/Luktwrdm/) or CodeSandbox (https://codesandbox.io/s/new) example below:
Here is the jsFiddle
By clicking on a title and checking the console, you should see that the setState
is called twice (its callback, however, is called only once).
What is the expected behavior?
Not sure, it might be related to #12094, then the behaviour might be intended. But when using the previous state to set the new one, then the component breaks if setState
is fired twice (for instance, toggle components don't work).
Which versions of React, and which browser / OS are affected by this issue? Did this work in previous versions of React?
React 16.3
The prevState => nextState
callback is fired twice, but I don't see the behavior you're talking about with toggle components not working. If you add some more logging or rendering to your example, you'll see that the toggle in it does in fact work correctly:
It is expected that setState updaters will run twice in strict mode in development. This helps ensure the code doesn't rely on them running a single time (which wouldn't be the case if an async render was aborted and alter restarted). If your setState updaters are pure functions (as they should be) then this shouldn't affect the logic of your application.
@gaearon Consider this example
this.setState(({counter}) => {
return { counter: counter + 1 };
});
say this piece of code will be triggered by clicking on a button. If setState
updater function can be called multiple times under StrictMode
, then how do I guarantee that my counter
only increments by 1 per click?
Actually, this is a real example in my project, where I have a button, and setState
was triggered after I clicked on the button. I noticed that the updater function in my setState
was called twice, resulting in inconsistency.
@franklixuefei the updater should be called twice with the same state. For example, if counter
is 0 it will be called with 0 twice, returning 1 in both cases.
Also I believe only one of the invocations actually cares about the value returned. So React isn't processing each state update twice, it's just calling the function twice to help surface issues related to side effects in a state updater and other methods that should be pure.
@aweary Thanks for answering my question! However, this is not the case. Take a look at the below real-life example from my project:
private handleItemRemove(index: number) {
const { onItemsChange } = this.props;
this.setState(
({ selectedItems }) => {
console.log('updater function called', selectedItems);
// remove the item at index
if (selectedItems.length > 0) {
// remove one item at the index
selectedItems.splice(index, 1);
}
return { selectedItems: [...selectedItems] };
},
() => {
// tslint:disable-next-line:no-unused-expression
this.inputNode && this.inputNode.focus();
// tslint:disable-next-line:no-unused-expression
onItemsChange && onItemsChange(this.state.selectedItems);
}
);
}
where selectedItems
is just an array of objects. I noticed that in this example, the updater function was called twice (can be seen from the console log), and the second time the updater function was called, selectedItems
contains one fewer element than the first time. Trust me, I was banging my head on this bug last night...
splice
mutates the array so this looks like the type of impurity that strict mode is intended to catch :smile:
@iamdustan I noticed that too, but if you look closely, I created a new array at the end:
return { selectedItems: [...selectedItems] };
Oh wait... You are right! It modified the original array even though I returned a new array!
I'm so surprised that I didn't even think of it at first. What a shame.
Now I realized how great React.StrictMode
is.
Thanks @iamdustan and @aweary
Similarly, constructor()
is called twice with the same props. If you're dispatching a loading action that triggers API fetching and more action dispatching via something like saga then it makes development pretty confusing. You might see flashes of UI changes as the component enters its loading state multiple times.
I'm not sure how helpful this is for day to day development. I guess it's just a downside to relying on side effects with Saga?
I am experiencing the same "issue" (setState
updater function called twice) even if I do not use React.StrictMode
(I am using React 16.8.6). Is this behaviour enabled by default on React 16.8.6?
Surely execution of setState (and its count of execution) is a functional req and should be identical across development and production modes?
Only difference between dev and prod should be data or non-functional reqs IMO.
The whole purpose of StrictMode is to find cases where the user-written functions that are supposed to be pure are, in fact, not pure. If they are pure, then there is no observable difference between development and production modes.
f they are not pure, Strict Mode helps find that issue. This is to prepare for the future, where React doesn't give any guarantees about the number of times they would be invoked, even in production. You can opt out of Strict Mode if you don't want this behavior, but it is the very purpose of Strict Mode.
Executing twice to detect an intermittent error in a potentially 'unpure' function is arbitrary. They could argue you need 10 executions so I'm struggling to see why this is a justification for Strict Mode at the package level, when it should and could be a user-defined mode.
Executing twice to detect an intermittent error in a potentially 'unpure' function is arbitrary.
Not an error, but a mutation or a side effect. Yes, it is arbitrary, but we've found it works really well in practice. In fact, you can see an example of this right above in the comments: https://github.com/facebook/react/issues/12856#issuecomment-393643981. A "proper" solution to this would involve having some way to track effects on the language level. There are languages that do this (e.g. Koka) so it's conceivable that eventually JS type systems might be able to express some version of this. In the meantime, we use what works in practice.
They could argue you need 10 executions so I'm struggling to see why this is a justification for Strict Mode at the package level, when it should and could be a user-defined mode.
I'm not sure what you mean by either "justification at the package level" or a "user-defined mode". Strict Mode is a part of React precisely because user code has no control over how React calls its functions. So it's React that needs to execute these functions twice (if you can get onboard with this heuristic). You're welcome to remove StrictMode
from your app if that confuses you.
The whole purpose of StrictMode is to find cases where the user-written functions that are supposed to be pure are, in fact, not pure. If they are pure, then there is no observable difference between development and production modes.
f they are not pure, Strict Mode helps find that issue. This is to prepare for the future, where React doesn't give any guarantees about the number of times they would be invoked, even in production. You can opt out of Strict Mode if you don't want this behavior, but it _is_ the very purpose of Strict Mode.
@gaearon Is Strict Mode enabled by default as of React 16.13.1?
Thanks
Same issue here.
handleChange(id){
this.setState(state => {
const newtodos = state.todos.map(todo => {
if(todo.id === id){
todo.completed = !todo.completed
}
return todo
})
return {todos: newtodos}
})
}
The handleChange
function is called for handling the checkbox onChange
event. Due to strict mode, it doesn't change the value of the completed
. Because it is called twice it goes back to the previous state. I am using the completed
to check or uncheck the checkbox. So is this not a pure function?
@akhileshkcoder In your case, you are mutating the nth todo
when todo.id === id
, so, even though newtodos
is a new array when you return the next state at return {todos: newtodos}
, your setState
callback function is not pure.
Try this instead:
handleChange(id){
this.setState(state => {
const newtodos = state.todos.map(todo => {
if(todo.id === id){
return {
...todo,
completed: !todo.completed
};
}
return todo
})
return {todos: newtodos}
})
}
This way the setState's callback is idempotent and even if React calls it more than once, it will lead to the correct state.
@tonix-tuft Cool!. It worked. Thank you.
Most helpful comment
splice
mutates the array so this looks like the type of impurity that strict mode is intended to catch :smile: