Optimistic Updates React - Speed Up Your Application
In this post you will learn what are optimistic updates and how to implement them inside React without any libraries.
In order to understand that goal of optimistic updates lets look on our example.
Project
I already prepared for us a React component which is called AddToFavorites
.
This is just a button with the count of people who favorited something, for example an article, and we can click on it in order to favorite. The main point is that this component is a part of the real project. When we click on this button there is a real API request to the backend and it will update the information in the database.
When we click on it nothing happens for 2 second and only after we got a response from the backend we update the counter and colors on our button.
Our main problem is that huge delay until API call is done
On your real project it might be smaller but there is some delay anyway. Why is that? Because we are making an API call and it takes time. Here we are waiting for the response and only when it is successful we update our client.
This is how it is done in the component.
const AddToFavorites = (props) => {
const [isFavorited, setIsFavorited] = useState(props.isFavorited);
const [favoritesCount, setFavoritesCount] = useState(props.favoritesCount);
const buttonClasses = classNames({
btn: true,
"btn-sm": true,
"btn-primary": isFavorited,
"btn-outline-primary": !isFavorited,
});
const handleLike = (event) => {
event.preventDefault();
// Simulating slow backend
setTimeout(() => {
axios
.patch(`http://localhost:3004/articles/${props.articleId}`, {
isFavorited: isFavorited ? false : true,
favoritesCount: isFavorited ? favoritesCount - 1 : favoritesCount + 1,
})
.then((response) => {
setFavoritesCount(response.data.favoritesCount);
setIsFavorited(response.data.isFavorited);
})
}, 2000);
};
return (
<button className={buttonClasses} onClick={handleLike}>
<i className="ion-heart"></i>
<span> {favoritesCount}</span>
</button>
);
};
We provide inside intial value if our article is favorited or not. The function handleLike
is exactly what sends a request to the backend. As you can see it put API call in timeout of 2 seconds to simulate the long request time. After we got the correct response back we update our state of the component. And again this is a real component from the real project done in a way how we normally create such functionality.
The problem is that we don't show a user immediate changes after his interaction.
Yes our change comes later when we are sure that it was written to the database but for user it feels like website is not working or it lags. How we can improve it?
Ways to improve
Either we can make our backend faster to get our respond faster or we can use a thing which is called optimistic updates. It is not always possible to make an API faster but we can always leverage our client to make things feeling faster. What does optimistic updates means?
It means that we optimistically update our client but we don't really know yet if our API call was successful or not.
Which actually means in our case here we can directly highlight user changes before we send this request to the backend. Probably this request will be successful later but user will see his changes immediately.
This is exactly optimistic update. We optimistically think that our request will be successful and we can update our client so the user see immediate feedback. It makes a lot of sense to do such stuff when you add new item to the todo list or you like some article. It all makes your client feel much faster.
Adding optimistic updates
Here we want to do this without any libraries. As you saw in our API call we provide inversed value of isFavorited
and favoritesCount
. We can create 2 future properties and set it in the state even before we do an API call.
const futureIsFavorited = isFavorited ? false : true;
const futureFavoritesCount = isFavorited
? favoritesCount - 1
: favoritesCount + 1;
setFavoritesCount(futureFavoritesCount);
setIsFavorited(futureIsFavorited);
// Simulating slow backend
setTimeout(() => {
axios
.patch(`http://localhost:3004/articles/${props.articleId}`, {
isFavorited: futureIsFavorited,
favoritesCount: futureFavoritesCount,
})
.then((response) => {
setFavoritesCount(response.data.favoritesCount);
setIsFavorited(response.data.isFavorited);
})
We moved the values from the request in 2 properties before it and update our favoritesCount
and isFavorited
even before we start the request. We will still update them both after successful response but most likely the values will be the same.
As you can see in browser now we see immediate change in the browser when we click on the button.
When API is broken
But it is not enough. If our API for some reason gives us an error then we are lying to our user about the change that didn't happen. This is why it is important to revert values back to previous if our request fails.
const previousIsFavorited = isFavorited;
const previousFavoritesCount = favoritesCount;
...
setTimeout(() => {
axios
.patch(`http://localhost:3004/articles/${props.articleId}`, {
isFavorited: futureIsFavorited,
favoritesCount: futureFavoritesCount,
})
.then((response) => {
setFavoritesCount(response.data.favoritesCount);
setIsFavorited(response.data.isFavorited);
})
.catch(() => {
setFavoritesCount(previousFavoritesCount);
setIsFavorited(previousIsFavorited);
});
Here we additionally stored 2 values before changing then and added a catch
part for our request. If we come to catch
it means that our request fails and we need to revert our values to the previous once. Now even when API is broken we are on the safe side with our code.
Want to conquer your next JavaScript interview? Download my FREE PDF - Pass Your JS Interview with Confidence and start preparing for success today!
📚 Source code of what we've done