Reputation: 6655
In the example below I am using an ES6 Map as a state value in React:
class App extends React.Component {
constructor(props) {
super(props);
const results = new Map();
results["group1"] = [{ value: "..." }, { value: "..." }];
this.state = { results };
}
onUpdateClick(i) {
this.state.results["group1"][i].value = i;
this.setState({});
}
onResetClick(i) {
this.state.results["group1"][i].value = "...";
this.setState({});
}
render() {
const { results } = this.state;
return (
<div>
{results["group1"].map((r, i) => (
<div>
{r.value}
<button onClick={e => this.onUpdateClick(i)}>update</button>
<button onClick={e => this.onResetClick(i)}>reset</button>
</div>
))}
</div>
);
}
}
ReactDOM.render(<App />, document.getElementById("container"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id='container'></div>
When you click the button, I directly update the Map, in place and then call setState with no arguments. I do not make a clone/deep copy of the map. Based on my understanding of the React docs, this should not work and is explicitly warned against in the docs:
Never mutate this.state directly as calling setState() afterwards may replace the mutation you made. Treat this.state as if it were immutable
(https://reactjs.org/docs/react-component.html#state)
The docs also state that comparisons are shallow, so calling with an empty object should surely result in no merge and therefore no re-render?
Why does this example work?
(also I should note that I also reproduce this behaviour with React v16.9.0)
Edit: I also want to point out (since many answers refer to the fact that I am passing an empty object) that the component is re-rendered (and updated) if I call setState like so:
this.setState({ results: this.state.results })
which seems like it should not cause a re-render
Upvotes: 3
Views: 1732
Reputation: 663
According to docs, setState() will always lead to a re-render unless shouldComponentUpdate() returns false. You can think of the render() function as creating a tree of React elements. On the next state or props update, that render() function will return a different tree of React elements(Virtual DOM).
At any point in time, the React library maintains two copies of the Virtual DOM.
When the request to setState() is triggered, React creates a new tree containing the reactive elements in the component (along with the updated state). This tree is used to figure out how the component’s UI should change in response to the state change by comparing it with the elements of the previous tree.
The Virtual DOM which is then synced with real DOM through process called reconciliation.
As for mutations, not doing direct mutations would avoid unnecessary bugs in your project because setState() is asynchronous which means you cannot expect setState to update state immediately(it does in batches) so any previous mutations could be overridden by previous setState state update operation.
Refs-
Upvotes: 2
Reputation: 85545
You're wrong:
this.setState({}); // this will do nothing.
You are expecting that the state should be updated with empty object. But it is not.
When you use setState method, it expects properties to update. But since you do not provide any property to setState, it will do nothing. But it will still re-render your component by setState nature.
Update: To your query
Edit: I also want to point out (since many answers refer to the fact that I am passing an empty object) that the component is re-rendered (and updated) if I call setState like so:
this.setState({ results: this.state.results })
which seems like it should not cause a re-render
React internally states that it is similar to this.state.results
when you try to update state with exact same state. So, there's nothing to re-render. But when you use setState with empty object, React will flash its state so the re-render occurs.
Read this note from the docs:
Note
Avoid copying props into state! This is a common mistake:
constructor(props) {
super(props);
// Don't do this!
this.state = { color: props.color };
}
The problem is that it’s both unnecessary (you can use this.props.color
directly instead), and creates bugs (updates to the color prop won’t be reflected in the state).
Upvotes: 1
Reputation: 147
When calls this.setState
, basically React will merge the parameter we pass to this function with the old this.state
to produce a new state value. For example:
this.state = Object.assign({}, this.state, passedParam);
So in above snippets, every time you call this.setState({});
a new state object has been created which make Component re-rendered.
Upvotes: 0