You may call setState() immediately in componentDidUpdate() but note that it must be wrapped in a condition like in the example above, or you'll cause an infinite loop. It would also cause an extra re-rendering which, while not visible to the user, can affect the component performance.
What happens when you call setState() inside render() method? Nothing happens.
render() Calling setState() here makes it possible for a component to produce infinite loops. The render() function should be pure, meaning that it does not modify a component's state. It returns the same result each time it's invoked, and it does not directly interact with the browser.
According to the React Documentation it's perfectly OK to call setState() from within the componentDidMount() function. It will cause render() to be called twice, which is less efficient than only calling it once, but other than that it's perfectly fine.
You can use setState
inside componentDidUpdate
. The problem is that somehow you are creating an infinite loop because there's no break condition.
Based on the fact that you need values that are provided by the browser once the component is rendered, I think your approach about using componentDidUpdate
is correct, it just needs better handling of the condition that triggers the setState
.
The componentDidUpdate
signature is void::componentDidUpdate(previousProps, previousState)
. With this you will be able to test which props/state are dirty and call setState
accordingly.
componentDidUpdate(previousProps, previousState) {
if (previousProps.data !== this.props.data) {
this.setState({/*....*/})
}
}
If you use setState
inside componentDidUpdate
it updates the component, resulting in a call to componentDidUpdate
which subsequently calls setState
again resulting in the infinite loop. You should conditionally call setState
and ensure that the condition violating the call occurs eventually e.g:
componentDidUpdate: function() {
if (condition) {
this.setState({..})
} else {
//do something else
}
}
In case you are only updating the component by sending props to it(it is not being updated by setState, except for the case inside componentDidUpdate), you can call setState
inside componentWillReceiveProps
instead of componentDidUpdate
.
This example will help you to understand the React Life Cycle Hooks.
You can setState
in getDerivedStateFromProps
method i.e. static
and trigger the method after props change in componentDidUpdate
.
In componentDidUpdate
you will get 3rd param which returns from getSnapshotBeforeUpdate
.
You can check this codesandbox link
// Child component
class Child extends React.Component {
// First thing called when component loaded
constructor(props) {
console.log("constructor");
super(props);
this.state = {
value: this.props.value,
color: "green"
};
}
// static method
// dont have access of 'this'
// return object will update the state
static getDerivedStateFromProps(props, state) {
console.log("getDerivedStateFromProps");
return {
value: props.value,
color: props.value % 2 === 0 ? "green" : "red"
};
}
// skip render if return false
shouldComponentUpdate(nextProps, nextState) {
console.log("shouldComponentUpdate");
// return nextState.color !== this.state.color;
return true;
}
// In between before real DOM updates (pre-commit)
// has access of 'this'
// return object will be captured in componentDidUpdate
getSnapshotBeforeUpdate(prevProps, prevState) {
console.log("getSnapshotBeforeUpdate");
return { oldValue: prevState.value };
}
// Calls after component updated
// has access of previous state and props with snapshot
// Can call methods here
// setState inside this will cause infinite loop
componentDidUpdate(prevProps, prevState, snapshot) {
console.log("componentDidUpdate: ", prevProps, prevState, snapshot);
}
static getDerivedStateFromError(error) {
console.log("getDerivedStateFromError");
return { hasError: true };
}
componentDidCatch(error, info) {
console.log("componentDidCatch: ", error, info);
}
// After component mount
// Good place to start AJAX call and initial state
componentDidMount() {
console.log("componentDidMount");
this.makeAjaxCall();
}
makeAjaxCall() {
console.log("makeAjaxCall");
}
onClick() {
console.log("state: ", this.state);
}
render() {
return (
<div style={{ border: "1px solid red", padding: "0px 10px 10px 10px" }}>
<p style={{ color: this.state.color }}>Color: {this.state.color}</p>
<button onClick={() => this.onClick()}>{this.props.value}</button>
</div>
);
}
}
// Parent component
class Parent extends React.Component {
constructor(props) {
super(props);
this.state = { value: 1 };
this.tick = () => {
this.setState({
date: new Date(),
value: this.state.value + 1
});
};
}
componentDidMount() {
setTimeout(this.tick, 2000);
}
render() {
return (
<div style={{ border: "1px solid blue", padding: "0px 10px 10px 10px" }}>
<p>Parent</p>
<Child value={this.state.value} />
</div>
);
}
}
function App() {
return (
<React.Fragment>
<Parent />
</React.Fragment>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
<div id="root"></div>
<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>
I would say that you need to check if the state already has the same value you are trying to set. If it's the same, there is no point to set state again for the same value.
Make sure to set your state like this:
let top = newValue /*true or false*/
if(top !== this.state.top){
this.setState({top});
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With