Can't update state in react component - reactjs

I have a component AddMenu
constructor(props) {
super(props);
this.state = {
menu: '',
make: '',
newCar: '',
}
this.addConfirmFuction=this.addConfirmFuction.bind(this);
}
Make the menu, make, model, year updated by changing the input text, it works.
handleChangeEvent(e) {
this.setState({
[e.target.name]: e.target.value,
});
}
Then I want to combine everything in the state to "newcar" in the state
addConfirmFuction() {
let newEntered=[];
newEntered=newEntered.concat(this.state.menu);
newEntered=newEntered.concat(this.state.make);
this.setState({newCar:newEntered,});
}
After run the last line of code: this.setState (newCar: newEnteredCar), it is not updated, it is still ''. I looked at lots of documents and couldn't figure out...I have bind the function in the state constructor, I have used setState function, but couldn't update the state.thank you in advance.

Did you check if the state values "make" and "menu" are really not empty inside "addConfirmFunction()"?
Also, from your code, it looks like "make" and "menu" are just strings. Any reason for "newCar" being set with a value of type array? If it need not to be array, then this.state.make + this.state.menu would suffice I guess!
Edit:
I think I got the problem. If you are checking the state value immediately after the line is called, then you might not see the new value there since setState is an async function in React. (For performance gains). Please read this stack overflow link. There are lots of article which talks about it.
If you want to see the new value, since this setState calls for a render, keep a breakpoint at render method and check if the value of newCar is available there.

Related

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.

calling a function using this.value

I want to pass the appropriate makeData function into the data element in ReactTable according to which year is selected. I keep getting a syntax error and I'm not sure why it won't let me compile it as is.
My logic is this:
The select value has an onChange function that will call handleChange whenever a different year is selected. Once handleChange is called, it will set the value to the event target.
The data object will then pass to the "data" element in ReactTable as "this.value"
There's a lot of stuff wrong here:
Functional components don't have state. Use a class that extends React.Component if you need state
handleChange can't be placed inside of the JSX. Again, use a class and make that a method of the class
this.state.value has the function in a string. You probably want this.state = {value: makeData2014()};. You do this in your JSX as well. Wrap the function call in curly braces instead of strings like so: <option value={makeData2014()}>2014</option>
When you call setState, you're updating this.state.value, not this.value. Thus, you don't need this.value at all and you should remove it.
To top it off, you didn't add the specific error message, so I can't even be sure this is all of it.
Step one should be fixing up all of these issues. After that, if you still have any problems, update your question with the new code and the specific error message you're getting.

Resources