How to assign a prop value to a two state in react - reactjs

I have searched for this text but none fixed my situation. my condition is I have one prop value I need to set this value to 2 state variable.one is direct assignment one is indirect. now due to async nature of set state or unknown reason before setting that value child got rendered. now how should I control this behavior?
this is my componentWillReceiveprops of child
componentWillReceiveProps(nextProps) {
let lastManualFlag =[...this.state.lastManualFlag];
console.log('before',lastManualFlag,nextProps.groupBy.length);
for(var i=0;i<nextProps.groupBy.length;i++){
lastManualFlag[i]=false;
}
console.log("after",lastManualFlag)
this.setState({lasManualProg:lastManualFlag});
this.setState({ groupBy: nextProps.groupBy});
}
this is my render method
render() {
console.log('this.state',this.state);
if (!this.props.groupBy) return null;
when I saw console my lastManualFlag is still size 0 array

You have two calls to set state
how about do it in one call like:
this.setState({...state,lastManualProg:lastManualFlag,groupBy: nextProps.groupBy});

Related

ComponentDidUpdate() keeps looping

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

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.

React - How can I make one time only rendering value in state

What I need to do is to setState with a value, then send data to a children by props, but I would like "state" to forget about that after doing this once.
this.setState({
animateId: 15 //or any number of id
});
then later
if(this.state.animateId){
let idToSent = this.state.animateId;
}
<Items <...different props> animate={idToSent}/> //once after settingState - 15,
// then undefined or whatever with every next time
and I would want to take this value once, send it via props, and forget about it, either way.
Is there any way to do that, beside just altering that one value in state again, because that would cause unnecesary rendering ??
We can set it to undefined like this after our task is complete
this.setState({ doSomething: undefined });
I would let the Item component keep track of when and when not to initiate the animation based on comparing the latest props with the local state.
I don't know exactly how you are triggering the animation, but it sound like you could do something like this in the Item component
const { animateId } = this.props;
if (animateId !== this.state.animateId) {
// Whatever code that starts your animation...
// Then either update the state right away to signal the last animation
// that was triggered, or if it can't be done here, then set it in a
// callback from when the animation is done.
this.setState({ animateId });
}
Just keep in mind that if this code is executed during the render phase, then it probably won't let you call setState.

React: setting state and default input from prop

I'm creating a app that allows users to create assessments (that other users can complete). Right now I am working on an 'edit' page, where a users loads a form that is prefilled with the relevant data - but he is then able to change these values.
However, I am having trouble with two things (that I suspect are related).
First: the input fields wont display a default value that is derived from the component state.
Second: If I set the input fields directly from the props I am no longer able to change the values.
The components gets passed a prop that is called block_data which is a dict containing key/value pairs of strings.
I'm attempting to convert load it into the state like so
constructor(props) {
super(props);
this.state = {
block: this.props.block_data,
question_list: [],
question_options: ['BO', 'SC', 'SR', 'NO'],
block_list: [],
};
(..)
}
However, this does not work. When I check in the chrome react extension the state is not set.
The input fields are all very simular to the example I've included below. Here I've set its value from the props. In this case it does display the correct initial data. But I am unable to edit it.
<input
onChange={e => this.changeIsANaturalPartOfLife(e)}
value={this.props.block_data.title}
name="title"
/>
Below is the 'on change' function. When I check the chrome react tool, I can see that only the first letter of the state is updated when I start typing. The input field does not show any changes.
changeIsANaturalPartOfLife(e, optional) {
const target = e.target;
const name = target.name;
const value = target.value;
console.log(value);
this.setState({ block: {[name]: value }});
}
I am really at a loss here on what to do. What I am trying to do here seems simple enough, yet I'm unable to advance beyond this flawed stage. Does anyone have an idea what I am doing wrong?
As you mentioned in comment: "the data is loaded from a DjangoRestAPI".
Solution of first problem:
You need to use componentwillreceiveprops lifecycle method to update the state with new props values (after successfully fetched from server) otherwise state of child component will always have the initial data of parent component.
As per DOC:
componentWillReceiveProps() is invoked before a mounted component
receives new props. If you need to update the state in response to
prop changes (for example, to reset it), you may compare this.props
and nextProps and perform state transitions using this.setState() in
this method.
Use this method:
componentwillreceiveprops(nextProps) {
// compare nextProps.block_data and this.state.block if not equal
this.setState({
block: nextProps.block_data
})
}
Solution of second problem:
When you are directly using the props instead of storing into state, then you need to update the value in parent component during onChange, because you are using this.props.value and updating this.state.value, and hence props.value will always be same and it will not allow you to type anything.

Resources