I'm working on a code where I have an empty array which has to be filled after a certain request returns.
I tried to setState of the array like that:
const [recentTransactionContacts,setRecentTransactionContacts] = useState<object[]>([])
///// more code
Contacts.getAll((err, contacts) => {
if(err) throw err
if(contacts.length > 0)
setRecentTransactionContacts(prevState => ({...prevState.recentTransactionContacts, ...contacts}));
else
ToastAndroid.show(`There no contacts on your phone. Can't transfer`, ToastAndroid.SHORT)
})
Now, I don't see any change in the array of contacts after trying to do setState that way.
The only way, that I could update the state was like that:
setRecentTransactionContacts(contacts)
However, I don't think that the latter is the correct way.
What is the correct way to update the state of an array?
I think this should be served your case, the recentTransactionContacts is not changed after this line runs so you do not need to access to prevState to do what you want.
setRecentTransactionContacts([...recentTransactionContacts, ...contacts]);
Using prevState or updater function is required in cases where the current state might be changed or stale, which doesn't seem to be the case here. Infact it could be the prevState itself causing issues, as the prevState value could be undefined at run.
As stated, replace your method to
setRecentTransactionContacts([...recentTransactionContacts, ...contacts]);
which will take in the current state value and append the contacts to the state.
Related
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.
So this is my code for component did update and this.props.getQuestions() is constantly firing. questions property is an array of questions which takes the values of the global state.
componentDidUpdate(prevProps){
if(prevProps.questions !== this.props.questions){
this.props.getQuestions();
}
}
Connecting state to props
const mapStateToProps = state => ({
questions: state.question.questions
});
When I try to compare the length of array between prevState and the current state, the method is never fired.
componentDidUpdate(prevProps){
if(prevProps.questions.length !== this.props.questions.length){
this.props.getQuestions();
}
}
How to make it work, so this method is fired only when the array is updated?
Simple array compare doesn't work. It will always match your condition ie. always not equal. And thus, componentDidUpdate fires constantly. Rather, you should check it like:
if(JSON.stringify(prevProps.questions) !== JSON.stringify(this.props.questions)){
this.props.getQuestions();
}
Regarding the checking with length property, it should work fine. But you stated that it isn't working. It seems you get same array when you use getQuestions. Check that function properly that it gets updated questions.
Your condition for fire was not right which you can see with your comparison of length of the array. You are checking for the equality of array's reference which is anyways incorrect(not sure how redux handles the prop update though).
You drill down to specificity of the questions in comparison and can compare questions' length, questions' questionId etc.. Loop through them and identify as desired.
You can even use library like lodash https://lodash.com/docs to compare array's and specific property.
If questions is the only property in mapStateToProps() you could just ensure you create a new array wherever you update it
newState.question.questions = [...updatedArray];
and remove the if statement from your componentDidUpdate entirely.
Make sure you do not update the questions array when calling getQuestions(), doing that can cause infinite loops where your didUpdate function causes an update (triggers didUpdate, ad infinitum)
locationHistory is always an empty array in the following code:
export function LocationHistoryProvider({ history, children }) {
const [locationHistory, setLocationHistory] = useState([])
useEffect(() => history.listen((location, action) => {
console.log('old state:', locationHistory)
const newLocationHistory = locationHistory ? [...locationHistory, location.pathname] : [location.pathname]
setLocationHistory(newLocationHistory)
}), [history])
return <LocationHistoryContext.Provider value={locationHistory}>{children}</LocationHistoryContext.Provider>
}
console.log always logs []. I have tried doing exactly the same thing in a regular react class and it works fine, which leads me to think I am using hooks wrong.
Any advice would be much appreciated.
UPDATE: Removing the second argument to useEffect ([history]) fixes it. But why? The intention is that this effect will not need to be rerun on every rerender. Becuase it shouldn't need to be. I thought that was the way effects worked.
Adding an empty array also breaks it. It seems [locationHistory] must be added as the 2nd argument to useEffect which stops it from breaking (or no 2nd argument at all). But I am confused why this stops it from breaking? history.listen should run any time the location changes. Why does useEffect need to run again every time locationHistory changes, in order to avoid the aforementioned problem?
P.S. Play around with it here: https://codesandbox.io/s/react-router-ur4d3?fontsize=14 (thanks to lissitz for doing most the leg work there)
You're setting up a listener for the history object, right?
Assuming your history object will remain the same (the very same object reference) across multiple render, this is want you should do:
Set up the listener, after 1st render (i.e: after mounting)
Remove the listener, after unmount
For this you could do it like this:
useEffect(()=>{
history.listen(()=>{//DO WHATEVER});
return () => history.unsubscribe(); // PSEUDO CODE. YOU CAN RETURN A FUNCTION TO CANCEL YOUR LISTENER
},[]); // THIS EMPTY ARRAY MAKES SURE YOUR EFFECT WILL ONLY RUN AFTER 1ST RENDER
But if your history object will change on every render, you'll need to:
cancel the last listener (from the previous render) and
set up a new listener every time your history object changes.
useEffect(()=>{
history.listen(()=>{//DO SOMETHING});
return () => history.unsubscribe(); // PSEUDO CODE. IN THIS CASE, YOU SHOULD RETURN A FUNCTION TO CANCEL YOUR LISTENER
},[history]); // THIS ARRAY MAKES SURE YOUR EFFECT WILL RUN AFTER EVERY RENDER WITH A DIFFERENT `history` OBJECT
NOTE: setState functions are guaranteed to be the same instance across every render. So they don't need to be in the dependency array.
But if you want to access the current state inside of your useEffect. You shouldn't use it directly like you did with the locationHistory (you can, but if you do, you'll need to add it to the dependency array and your effect will run every time it changes). To avoid accessing it directly and adding it to the dependency array, you can do it like this, by using the functional form of the setState method.
setLocationHistory((prevState) => {
if (prevState.length > 0) {
// DO WHATEVER
}
return SOMETHING; // I.E.: SOMETHING WILL BE YOUR NEW STATE
});
I was looking at some example react code (in the antd docs), and I noticed they have code that is equivalent to:
this.setState(prevState => { prevState.name = "NewValue"; return prevState; });
This looks a bit naughty, but does it actually break anything? Since it's using the arrow function it's not breaking the ordering of changes being applied even if React batches them up in the background.
Of course setState is intended to expect a partial state so there might be performance side effects there as it might try to apply the whole state to itself.
edit: (in response to #Sulthan)
The actual code is this:
handleChange(key, index, value) {
const { data } = this.state;
data[index][key].value = value;
this.setState({ data });
}
n.b. data is an array, so its just being copied by reference then mutated.
It's actually completely wrong as its not even using the arrow function to get the latest state.
It comes from the editable table example here: https://ant.design/components/table/
Your example can be also rewritten as:
this.setState(prevState => {
prevState.name = "NewValue"
return "NewValue";
});
When a function is passed to the state the important thing is not to mutate the passed parameter and return the new state. Your example fails both.
...prevState is a reference to the previous state. It should not be directly mutated. Instead, changes should be represented by building a new state object based on the input from prevState...
(from setState)
I am not sure whether it was ever possible to use setState like in your example but looking into the change log I really doubt it.
The title is no really clear but my question is pretty simple. I'm using flux pattern.
I have an event handler inside a reactclass, this handler takes an input from the user.
What I'm doing is taking this input and pass it to an action (then the action update stores and finally my state is updated).
handler(e) {
var newParams = this.state;
newParams.input = e.target.value;
MyActions.someAction(newParams);
}
As you can see I want to pass the current state completed with the new input to an action. The problem is that if I'm doing this way I'm modify directly the current state without passing by setState and it's not advised at all. I don't want to use setState in my handler I want to use setState on store change.
So my question is am I compelled to use something like underscore _.clone() or is it a simpler way to do this I haven't seen ?
In the case you want to update the state first, then execute an action, you can leverage the setState callback:
handler(e) {
this.setState({ input: e.target.value }, () => {
MyActions.someAction(this.state);
});
}
And yes, you need to create a new object, as what you are assigning is a reference. You have different ways to do so, other than _.clone:
Object.assign
Object.assign polyfill
ES7 Spread Properties