ComponentDidUpdate() keeps looping - reactjs

class RatingsComponent extends Component{
componentDidMount(){
console.log("inside component did MOUNT")
this.props.fetchReviews()
}
shouldComponentUpdate(nextProps){
console.log("inside SHOULD update")
return (nextProps.reviews !== this.props.reviews);
}
componentDidUpdate(prevProps){
if(prevProps.reviews !== this.props.reviews)
{ console.log("inside component did update")
// this.props.fetchReviews() //commented due to infinite looping and hitting API
}
}
render(){
console.log("inside render");
return null;
}
}
fetchReviews() is an action that hits the API through saga and updates reviews state through reducer. Code absolutely works fine with componentDidMount(). I was curious and wanted to handle change in data if we are on the same page. I tried adding actionListener but there was no change. I added componentDidUpdate() as above. It worked but API is being hit infinite times. But the value of prevProps.reviews and this.props.reviews are same. I can't understand why this is happening. Any help is appreciated.
P.s: Can't show the code inside render() due to official concerns, simply returning null also causes same issue.

The issue is in the condition: you are not checking if the list of the reviews contains the same IDs, but you are just saying that if reviews!==previous fire fetch. This means that every time you retrieve a new array of reviews, even if the content is equal, the fetch request is fired again.
The loop is:
UpdateComponent->ComponentUpdated->ConditionIsTrue->FireFetch->UpdateComponent
And condition is always true since you are controlling two arrays in different memory address
Try using a statement like this and see if it solve your issue:
//diff is an array that will contain all the differences between this.props and previousProps
let diff= this.props.reviews.filter(review=> !previousProps.reviews.includes(review));
//if there are differences, call fetch
if(diff.lenght>0){
this.props.fetchReviews()
}

If we compare two objects with the == will not give an appropriate result so better to compare with the object values.
For Example:
shouldComponentUpdate(nextProps){
console.log("inside SHOULD update")
return nextProps.reviews.id !== this.props.reviews.id;
}
In the above example, I have compared it with the id property you can compare any unique property or main property that is important for re-render

I'm guessing fetchReviews updates redux store with new array of objects .... which causes componentDidUpdate to fire ... and then if(prevProps.reviews !== this.props.reviews) will evaluate to true Even if reviews contains same data ... but they're pointing to different locations in memory

Related

React sometimes rerenders between two setStates, depending on setState order

I've got two simple useStates.
At one point, they're set, one after another. This happens within a useEffect with [] deps.
canvas.toBlob(blob => {
if (blob) {
setBounds([width, height]);
setUrl(URL.createObjectURL(blob));
} else console.log("no blob");
});
The above code (first set bounds, then url) causes the component to rerender once, with both states set properly.
However, the code below (first url, then bounds):
canvas.toBlob(blob => {
if (blob) {
setUrl(URL.createObjectURL(blob));
setBounds([width, height]);
} else console.log("no blob");
});
makes the component rerender twice. The first render has only the url set, then only on the second render, the bounds are set as well.
Why does changing the order of these two lines change the rerenders?
I think the reason is because you're inside a callback function, React isn't batching updates.
So the order of these states being set actually matters and somehow setting the url before the bounds is triggering an additional render which I cannot make out from the given code.

Wait for Redux props and then do some operations on the props in componentDidMount

I have a ChatBox component for my website, which displays the chat history of a user. The chat history is not ordered in my firestore, so I want to (1) sort it from the most to the least recent in componentDidMount once I get the data via Redux props (this.props.profile.chats_history) and (2) set the field "chatlist" in the state of ChatBox to the sorted array. The problem is that it takes time for props to be received and when the array.sort() method was called the console reported that the array was undefined. I tried to get around it by using async and await keywords but my solution did not work.
My solution
async componentDidMount() {
let chatlist;
await this.props.profile.chats_history;
chatlist = this.props.profile.chats_history.sort(function(a, b) {return a.time - b.time});
this.setState({
chatlist: chatlist
})
}
What you can do is wait for chats_history to be updated, using componentDidUpdate instead of componentDidMount. Here I'm doing a deep equal on this.props.chats_history.
const _ = require("lodash")
componentDidUpdate(prevProps, prevState) {
if (!_.isEqual(prevProps.profile.chats_history, this.props.profile.chats_history)) {
chatlist = this.props.profile.chats_history.sort(function(a, b) {return a.time - b.time});
this.setState({
chatlist: chatlist
})
}
}
Basically what's happening here is that as soon as the component mounts, this.props.chats_history will have some value, but it won't contain the actual list of values yet. At some point, this.props.chats_history will be loaded, and this will trigger the component to update.
componentDidUpdate is triggered every time either this.props or this.state is updated. The arguments prevProps and prevState that you see in my code are references to the values of this.props and this.state from immediately before the update occurred that triggered componentDidUpdate.
componentDidUpdate will be triggered many times, and you want to execute your sort function only when it's the loading of this.props.chats_history that has triggered it. To do this, you compare (with _.isEqual) prevProps.chats_history with this.props.chats_history. Is they're not equal, this means that this.props.chats_history was just modified (in this case, loaded), so you call sort only under these circumstances.
The reason I use _.isEqual from the lodash library is that if I did a == or === comparison, it would always return true because this.props.chats_history is an array, and it would therefore be comparing the references rather than the contents of the array. If you use _.isEqual it does a deep comparison and returns true only if each element of this.props.chats_history is equal to each element of prevProps.chats_history.
Since you then call this.setState(), componentDidUpdate will be called again, but the if block will return false and not run the sort code again.
Does that make sense?
You can use getDerivedStateFromProps instead.
static getDerivedStateFromProps(props, state) {
const sortedChat = props.profile.chats_history?[...props.profile.chats_history].sort((a,b)=>{/*logic*/}):[]
return { sortedChat };
}
You can optimize your renders by comparing the current data in state and received data in props. That depends on your data again. Personally, I'd keep a timestamp in profile.chats and only update the state when timestamp changes. Also, sort changes the original array order. So, clone before you sort like I did above.

Why does the first iteration of my update to an array in state does not appear? It will only appear after the second iteration?

I have an array postArray defined in state on Main.js.
this.state ={
step: 1,
// welcome
qNumber:1,
accountNumber:'',
amount:'',
txNumber:1,
postArray : []
}
I also have a function on Main.js which inserts new array element into postArray:
insertTx =() => {
// save transaction to array state
// create copy of the array
const copyPostArray = Object.assign([],this.state.postArray)
// insert one element into the array
copyPostArray.push({
txNumber: this.state.txNumber+"-"+this.state.accountNumber,
qNumber : this.state.qNumber,
accountNumber : this.state.accountNumber,
amount : this.state.amount
})
// save the values back to array state
this.setState({
postArray:copyPostArray
})
console.log(this.state.postArray)
console.log(this.state.txNumber)
console.log(this.state.qNumber)
console.log(this.state.accountNumber)
console.log(this.state.amount)
}
On CashDeposit.js, postArray is being updated whenever I call InsertTx function below:
continue = e => {
e.preventDefault();
this.props.nextStep();
//increment the txNumber
// this.props.incTxNumber();
this.props.insertTx();
Viewing the postArray on the console.log, it shows an empty array on first iteration. But for the second iteration, it will show the value for the first, on the third iteration will show value for the second and so on. Why does it not update current values?
setState does not happen right away. The state will always be the same values until the next render happens. If you update state, then in the same cycle reference state, you will get the old state. This would make it appear that you are one behind if you run something like:
this.setState(newValues)
console.log(this.state) // old values
Make sure that when you are referencing state you don't rely on a setState from another function. This is where hooks and useEffect come in handy.
The issue you're seeing is caused by the fact that setState does not set the state immediately, you can think of it like an asynchronous operation. So when you try to log the state values, you are getting old values because the state hasn't changed yet.
In order to get access to the new state value, you can pass a callback to setState as a second parameter: this.setState(newState, updatedState => console.log(updatedState))
This is because setState() does not immediately update state. You will not see the updated state until the next time render() is called. Because of how React reconciles, this is pretty fast, because React won't try to build the DOM until all the setState() calls have been shaken out. But it also means that, while you can't see the new state immediately in the console, you can rest assured that you will see it eventually, before it appears in the browser.
It does, however, mean you need to be sure you've got your initial state condition handled in your code. If you don't set up your state in your constructor, you'll have at least one go-around where you'll need to render without undefined state throwing errors, for example.

Before getting data from database, renderview is getting called in React js

In my project, we are using promises to fetch data from DB. Before data returned, the renderView() method is getting called multiple times. In this process, as per below code, else part is displaying and disappearing in the renderView method cycles. In this renderView method, we are making a check that if(recordsArray.length > 0)
render view of records
else
display that records are not there
At the point of if condition, in few cycles, the recordsArray length is 0 and after few cycles, recordsArray is getting filled.
Is there any way to call renderView after recordsArray get filled?
I agree with #Ortho Home Defense's comment, if we don't see whole or related code we can't help you very much. But as far as I understood if you don't want to invoke your renderView function many times you need to check your data somehow and then invoke it. This means a change in your logic like:
render() {
return (
<div>{recordsArray.length && renderView()}</div>
);
}
renderView = () { ...render data }
Like this nothing will be rendered until your data is filled. If you want to show something you can use ternary operator.
render() {
return (
<div>{!recordsArray.length ? <p>No data</p> : renderView()}</div>
);
}

react one state variable depends on multiple other states variables

I am coming from angular world. And I am still kinda new to react.
One question that I encountered is that:
In angular, we have $watch to watch one scope variables to change and update other variables. like watch B,C,D, and change of B,C,D will affect variable A
In react, I am trying to do the same thing. However, I am calling to setState(B,callbackB) to update A. A has a setState that has an impact in render
It seems like doing such works correctly for variable B. However, updating A will occur in next render cycle. And this.forceUpdate() doesn't seems work.
Is there anything that I am doing wrong?
Thanks
An example would help clarify your situation, but depending on what "updating A" entails, you could move it to your render function. E.g.
class Component extends React.Component {
handleUpdate(B) {
this.setState({B})
}
computeAFromB(B) {
...
return A
}
render() {
const A = this.computeAFromB(this.state.B)
return <div>{A}</div>
}
}
When transitioning from Angular to React, it's important to keep in mind that scope !== state. This may be helpful: https://facebook.github.io/react/docs/thinking-in-react.html
You cannot rely on setState to update instantly. Try passing the new value explicitly to setState A.
Here's my example code, I think I found an answer already.
https://jsfiddle.net/yoyohoho/Lo58kgfq/1/
in the example code in jsfiddle. The input field is a required field.
And the button should be disabled if there is nothing in the field.
The problem is that if you type something and you erase it, button is still active, until you type in 1 more key, which is next cycle.
I use input1Err to determine if formError should be disabled in checkErr()
checkErr(){
if(this.state.input1Err == false){
this.setState({formErr: false });
}else{
this.setState({formErr: true });
}
}
This is the reason that why the view change happen in next cycle. Because setState is asynchronous
I was using this.state.input1Err to determine formErr, which may yet be changed before the if statement there.
So I changed to the following closure, which solves my issue.
checkErr(){
const self = this;
(function(j){setTimeout(function(){
if(self.state.input1Err == false){
self.setState({formErr: false });
}else{
self.setState({formErr: true });
}
},10)})(self);
}
This seems be a quick fix. However, what if there is ajax call and hooked up to state.input1Err. Is there a way I can watch this variable, so when it changes complete, I can have some sort of callback?
Thanks

Resources