recently I have stumbled upon a dilemma on how to properly approach UI updates. The stack I am using at work is React + axios. Let’s say there is a user profile object held in the local state and the user wants to update his first name. Which way is better? Please let me know what I am missing here?
const handleUpdateUserProfile = async (newUserName) => {
const originalUserName = userProfile.name;
// Optimistically update the UI
setUserProfile(prev => ({ ...prev, name: newUserName }));
// Try to send the database request
try {
await axios.post("/some-endpoint", {...userProfile, name: newUserName });
} catch (error) {
// In case of an error go back to the previous value
setUserProfile(prev => ({...prev, name: originalUserName}));
console.error(error);
}
}
OR
const handleUpdateUserProfile = async (newUserName) => {
const originalUserProfile = structuredClone(userProfile);
const updatedUserProfile = { ...originalUserProfile, name: newUserName }
// Optimistically update the UI
setUserProfile(updatedUserProfile);
// Try to send the database request
try {
await axios.put("/some-endpoint", updatedUserProfile);
} catch (error) {
// In case of an error go back to the previous value
setUserProfile(originalUserProfile);
console.error(error);
}
}
I am aware that I should be also checking for the status code in the response object from the post request.
Thanks for all the suggestions.
2
Answers
Least code approach, will be to simply defer the update until the API is completed and successful. You can show a spinner while the update happens.
While reviewing the codes based on the things happening under the hood, it found a few comments and suggestions as listed below.
a) The first code uses an updater function in the below two calls of the state updater. This may not be an essential requirement although it will work. The point is that there will be two separate renders with respect to the two calls below. This is due to the fact that React will not or cannot batch under the event of an async code. Since no batching is going to happen, and it is going to be rendered individually, therefore there is no need to use an updater function as it is in this code. An updater function is mandatory when there will be a series of state updates in the same event which is not the case over here.
Therefore the essential code required for this case is quite correct in the second code. This code uses an expression or “replace with value” instead of an updater function. It does not look for the state values already queued up in the event as nothing being queued up in this case, instead it just replaces the state with the given values which is the exact need of this case. Again the reasoning for this suitability is that there are two separate renders for this event – one for the sync code and the other for async code in it.
As an aside, the key point in this case is not at all the variable prev, but the local variables originalUserName and originalUserProfile with respect to the two codes.
To generalize the point, the first state update call in any event should not really require an updater function. It can be a simple expression or “replace with value”. However, to execute a series of state updates, an updater function is a must.
A sample code based on the above points given below.
a) Please note that the code logs two logs in the console showing that there are two separate renders and no batching.
b) The local variable tempA is vital as far as the error case is concerned.
c) On clicking the button, the sync state updater updates the state to 1 and then on error, the async state updater reverts this change by setting 0. It gets this old value from the local variable tempA.
App.js
Test run:
On loading the app:
After clicking the button: