How to prevent infinite loop in componentDidUpdate - reactjs

I've got this code but it gives me an infinite loop.
How can I prevent it?
Or I need to use another lifecycle method??
componentDidUpdate(prevProps, prevState) {
if(prevState.input["purpose"] === "TEST" ){
this.setState({input: { ...this.state.input, number: "1"}})
}else if(prevState.input["purpose"] !== "TEST"){
this.setState({input: { ...this.state.input, number: "0"}});
}
console.log(this.state.input.number);
console.log(prevState.input["purpose"]);
}

In both cases of if-else, you are updating the state and you are checking for the purpose to be 'Test'
if its 'Test' you are updating the state and if it's not still you are updating state

You are updating the state in componentDidUpdate. Therefore, this function is called every time.
Both of your conditions call setState.

Following docs from React.js. You should not update state like this.setState inside componentDidUpdate without condition
https://reactjs.org/docs/react-component.html#componentdidupdate
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. If you’re trying to “mirror” some state to a prop coming from above, consider using the prop directly instead. Read more about why copying props into state causes bugs.
In this case, your if else condition always update state
Please, follow react component lifecycle as below

Related

this.setState() not working properly - react.js

componentWillMount() {
let dID=this.props.match.params.datumID;
this.setState({datumID: dID});
console.log(dID);
console.log(this.state.datumID);
}
I'm just trying use setState() method.
But the problem is it is not working.
Here is what I get as output:
First console.log() : 66
Second console.log(): null
this.setState accepts a second argument as callback which is fired after the state is successfully changed.
componentWillMount() {
let dID=this.props.match.params.datumID;
console.log(dID);
this.setState({datumID: dID},()=>console.log(this.state.datumID));
}
side note : Move your code to componentDidMount.
componentWillMount() is invoked immediately before mounting occurs. It
is called before render(), therefore setting state in this method
will not trigger a re-render. Avoid introducing any side-effects or
subscriptions in this method.
Think of setState() as a request rather than an immediate command to update the component. For better perceived performance, React may delay it, and then update several components in a single pass. React does not guarantee that the state changes are applied immediately.
If you need to set the state based on the previous state,
read about the updater argument below.
this.setState((prevState, props) => {
return {datumID: props.match.params.datumID};
});
Because SetState() is an asynchronous function, use below code
componentWillMount() {
let dID=this.props.match.params.datumID;
this.setState({datumID: dID},
function(){
console.log(this.state.datumID)});
console.log(dID);
}
and if possible try to avoid compenentWillMount since it would be deprecated soon
setState() being an asynchronous function it is in progress of updating the state and javascript being single-threaded language it will execute the next line of code. So if you want to see the state value set you have to do something like this.
componentDidMount=()=> {
let dID=this.props.match.params.datumID;
this.setState({
datumID: dID
},()=>{
console.log(dID);
console.log(this.state.datumID);
});
}
Also I would recommend you to use componentDidMount as willMount won't work in the future as it is deprecated
this.setState is an asynchronous function that takes time and passes the newly assigned
state value over all of the React's life-cycle functions in order to update a state and trigger the re rendering of a component. Your first console logs out the value that is assigned to set state and the next console logs out the value that is currently residing in the state this.state.datumID. if you need to perform operations after setting state you can pass a callback that is triggered after a state is successfully updated.
this.setState({datumID: dID}, () => console.log(this.state.datumID) ); // this will log the updated value
Another thing I want to point out is componentWillMount will wont work in the future release. If you want to do something after DOM is rendered, use componentDidMount or you can perform pre-render task in the constructor of your Class Component.

why componentDidUpdate run multiple times

I really don't understand the react life cycle. I run console.log in componentDidUpdate() and saw that it ran multiple times
Console showed that componentDidUpdate ran 3 times
Maybe I can give you an extra example when your issue happens since I cannot see your code.
componentDidUpdate(prevProps, prevState) {
const { something } = this.props;
if (prevProps.something !== something) this.apiCall();
console.log('something')
}
when you change your state or props, componentDidUpdate is being invoked, and apiCall function makes http request via fetch or axios, and change the state twice using setState function.
whenever state gets changed, new render() is being invoked and componentDidUpdate follows.
but the condition
if (prevProps.something !== something) this.apiCall();
may not be satisfied anymore, just console logging at this time instead of calling apiCall function which would trigger inifinite loop.
hope it helps.
componentDidUpdate() is invoked immediately after updating occurs.
your problem may be accorded because the state has been changed, props received or parent re-rendred.
if you are new with React, I recommend you to read the following article:
Post-Render with componentDidUpdate()

Is setState() inside componentDidMount() considered an anti-pattern

I've just had a look at this discussion about setState() inside componentDidMount().
You can see that after the render() function, the componentDidMount() function will be called by React. When you put a setState() call in componentDidMount() then you are causing the entire component tree be re-rendered not only the current component - not to forget, the current component did just finished with rendering.
And some people suggested to put setState() call inside componentWillMount(). In some cases, I want to get the height of a rendered element and store it as state, and the above method wouldn't work. I also had a look at the React official website, and it suggests to do Ajax call inside componentDidMount(), which again goes against the above idea.
So, am I wrong about putting setState() inside componentDidMount()? If yes, what should I apply as an alternative?
You may call setState() immediately in componentDidMount(). It will
trigger an extra rendering, but it will happen before the browser
updates the screen. This guarantees that even though the render() will
be called twice in this case, the user won’t see the intermediate
state. Use this pattern with caution because it often causes
performance issues. In most cases, you should be able to assign the
initial state in the constructor() instead. It can, however, be
necessary for cases like modals and tooltips when you need to measure
a DOM node before rendering something that depends on its size or
position.
React docs
Using DidMount makes it clear that data won’t be loaded until after
the initial render. This reminds you to set up initial state properly,
so you don’t end up with undefined state that causes errors.
Example
TLDR:
- If you have all needed data in constructor - assign state there
constructor(props) {
super(props);
// Don't call this.setState() here!
this.state = { counter: 0 };
}
Call async action, touch DOM in componentDidMount()
Your case with unknowing the height of a rendered element might be a valid excuse to use setState inside componentDidMount. However in such a case I would definitely add another lifecycle method,shouldComponentUpdate, to control the rerender issue.
Normally you don't want to do synchronous state setting inside componentDidMount, you should just put it in the constructor. However element height is a bit unique, since you can't actually get the height of an element until it's mounted and rendered into the DOM.
So normally not, but in the case of element height it's ok to do it in componentDidMount.
You can do Async call and you must do it inside the componentDidMount() lifecycle hook. But this will call the render method again.
If you don't want the re-render to happen again, use the shouldComponentUpdate() method to prevent re-rendering the DOM. Example as follows:
shouldComponentUpdate (nextProps, nextState) {
// check the condition here
if (nextState.something !== 'value') {
// stop re-rendering
return false;
}
// continue rendering
return true;
}

Is setting state directly after each other bad?

My scenario is:
this.setState({firstState:1})
if(condition){
this.setState({secondState:2})
}
firsState should always be set to 1 but the secondState should only be set to 2 if a certain condition is fulfilled.
Is this bad practice because state will be updated twice directly after each other? And will it affect performance?
Is this code better?
if(condition){
this.setState({firtState:1,secondState:2})
}else{
this.setState({firstState:1})
}
You can update it once:
const myAmazingState = {
firstState: 1,
secondState: condition ? 2 : this.state.secondState
}
And then update your state
this.setState({...myAmazingState})
Hope it makes sense
Even if you put multiple setState calls in sequence React may batch them into one transaction call.
So there is no disadvantage.
See React may batch multiple setState() calls into a single update for performance.
React actually provides a way for you to achieve just that. The trouble with your current approach, as was pointed out earlier, is that React may batch the setState, but you can use functional setState. Your code would look like this.
this.setState((state, props) => {
// update state
if(condition) {
//update state 2
}
// return a single new object for your new state
});
React.js is optimized to have setState called quite often, it will actually not always re-render everytime you call setState but in fact will bundle multiple state changes together into one re-render.
In order to change the state twice, you need to do the following to avoid any possible issues as this.setState is an asynchronous function.
this.setState({data: {}}, () => this.setState({data: {name: "tom"}}))
Here you can see, we are passing setState a callback to execute once it has finished setting the state.

Setting state by assignment

In react, is there a reason why someone would want to set the state of a variable by assignment instead of calling setState(...)
Example:
// accessing state var directly
this.state.myVar = 'changed state'
// instead of calling setState
this.setState({myVar: 'changed state'})
To me this seems like an anti-pattern. But maybe there's a good explanation why sometimes it's necessary?
It's necessary, because React has to know wether this.state is considered mutated. There is no dirty-checking in React. The state object is considered dirty when this.setState is called, and no further comparisons are made to its previous state. This answer might help explain this in more detail: Why is React's concept of Virtual DOM said to be more performant than dirty model checking?
Setting (mutating) the state directly will work in this case:
this.state.myVar = 'changed state'
However, it should be avoided according to the React docs:
NEVER mutate this.state directly, as calling setState() afterwards may replace the mutation you made. Treat this.state as if it were immutable.
The main problem with mutating the state is that it prevents some of the React lifecycle methods from working. For example, React's shouldComponentUpdate() method is often used to speed up the app when dealing with a large number of components. The method allows you to skip re-rendering a component if the state has been updated:
// Return false if you want to skip the `render()` method
shouldComponentUpdate: function(nextProps, nextState) {
return this.state.myVar === nextState.myVar;
}
The above will not work if you are mutating the state. this.state.myVar and nextState.myVar references are the same and therefore the above will always return true.

Resources