This is an issue I faced, investigated, and fixed and would like to share my experience with you.
I found that when you are using useState HOOK to maintain state and then update the state using the style setState({...state, updatedProperty: updatedValue}) in an async function, you may run into some concurrency issues.
This may lead the application in some cases to lose some data due to the fact that async function keeps an isolated version of the state and may overwrite data some other component stored in the state.
The fix in short:
You need to use either a reducer to update state or use the function updater version of set state, if you are going to update the state from an async function because the function updater gets the latest updated version of state as an argument (prevState)
setState(prevState => ({...prevState, updatedProperty: updatedValue});
Long Description:
I was developing data context to manage user's contacts saved on a database which is hosted on a cloud MongoDB cluster and managed by a back end web service.
In the context provider, I used useState hook to maintain the state and was updating it like the following
const [state, setState] = useState({
contacts: [],
selectedContact: null
});
const setSelected = (contact) => setState({...state, selectedContact: contact});
const clearSelected = ()=> setState({...state, selectedContact: null};
const updateContact = async(selectedContact) => {
const res = await [some api call to update the contact in the db];
const updatedContact = res.data;
// To check the value of the state inside this function, I added the following like and found
// selectedContact wasn't null although clearSelected was called directly after this function and
// selectedContact was set to null in Chrome's React dev tools state viewer
console.log('State Value: ' + JSON.stringify(state));
//The following call will set selectedContact back to the old value.
setState({
...state,
contacts: state.contacts.map(ct=> ct._id === updatedContact._id? updatedContact : ct)
});
}
//In the edit contact form submit event, I called previous functions in the following order.
updateContact();
clearSelected();
Problem
It was found that after selecteContact is set to null then set back to the old value of the selected contact after updateContact finishes awaiting for the api call's promise.
This issue was fixed when I updated the state using the function updater
setState(prevState => ({
...prevState,
contacts: prevState.contacts.map(ct=> ct._id === updatedContact._id? updatedContact : ct)
}));
I also tried using reducer to test the behavior given this issue and found reducers are working fine even if you are going to use regular way (without the function updater) to update the sate.