Written by Kayode Adeniyi✏️
Milliseconds can make or break user experiences, as frustration from delays can drive them away from your site. One strategy that has gained traction for its ability to deliver swift, responsive interactions is the optimistic UI pattern. This pattern allows you to update a UI immediately after a user completes an action, based on the assumption that the background operations will succeed. This method increases the perception of an app’s speed and greatly improves user experience by providing instant feedback.
In this article, we will explore React’s new useOptimistic
Hook and how it can allow you to implement optimistic UI updates to make your application feel faster and more responsive.
What is the React useOptimistic
Hook?
The useOptimistic
Hook is a powerful feature currently available in React’s Canary version. It lets you show a different state while an asynchronous action, such as a network request, is underway. This is particularly important in cases where users expect instant feedback for their actions, such as when submitting forms or commenting on social media.
By implementing the useOptimistic
Hook, applications can update user interfaces immediately, providing a seamless and engaging user experience. Let's break it down with an example:
const [optimisticComments, postComment] = useOptimistic(commentsFromServer, addComment);
-
optimisticComments
: This is the optimistic state, initialized to the value ofcommentsFromServer
. It shows the current list of comments on a post, immediately reflecting any user actions -
postComment
: This is the dispatch function that you call to apply the mutation defined inaddComment
. This function updatesoptimisticComments
optimistically when a user adds a comment -
commentsFromServer
: This is the source of truth, representing the confirmed list of comments fetched from the server. If this value changes (e.g., after fetching new data from the server),optimisticComments
will also update to match, ensuring consistency -
addComment
: This is the mutation logic that will be applied tooptimisticComments
whenpostComment
is called. It defines how to add a new comment to the list optimistically
Considering developer efficiency, React introduced the useOptimistic
Hook tailored for this UI pattern. The optimistic UI update ensures that users are not left waiting for server responses, which is crucial for maintaining a smooth user experience. When a user performs an action, useOptimistic
reflects the state changes immediately before the server responds.
This hook manages the optimistic state by initially assuming that the API will respond successfully. If the API responds successfully, the assumed state is persisted. In case of an API failure, the state reverts to its original state. This balance is crucial for maintaining data integrity while offering a user experience that feels fast and responsive.
By using the useOptimistic
Hook, developers can create applications that feel more immediate and engaging, keeping users actively involved and satisfied with the performance.
A simple example: Commenting on a post
Imagine a social media platform where users can see posts from others. When a user hits the Send button, the app waits for the server’s response before updating the UI. This delay can make the app feel sluggish.
Assuming you have a server set up with the necessary files, let’s proceed to implement the optimistic functionality:
import { useState } from 'react';
import { useOptimistic } from 'react';
function CommentButton({ postId }) {
const [comments, setComments] = useOptimistic([]);
const [newComment, setNewComment] = useState('');
const [error, setError] = useState('');
const handleCommentSubmit = async () => {
setError(''); // Clear any previous errors
const optimisticNewComment = { id: Date.now(), text: newComment, status: 'sending' };
setComments([...comments, optimisticNewComment]); // Optimistically update the comments list
try {
const response = await api.postComment(postId, newComment);
const updatedComments = comments.map(comment =>
comment.id === optimisticNewComment.id ? { ...comment, status: 'sent' } : comment
);
setComments(updatedComments); // Update comment status to 'sent'
} catch (error) {
const filteredComments = comments.filter(comment => comment.id !== optimisticNewComment.id);
setComments(filteredComments);
setError('Failed to post comment. Please try again.');
}
};
return (
<div>
<input
type="text"
value={newComment}
onChange={(e) => setNewComment(e.target.value)}
placeholder="Write a comment..."
/>
<button onClick={handleCommentSubmit} disabled={!newComment.trim()}>
Post Comment
</button>
{comments.map(comment => (
<div key={comment.id}>
{comment.text} {comment.status === 'sending' && <span>(Sending...)</span>}
</div>
))}
{error && <p style={{ color: 'red' }}>{error}</p>}
</div>
);
}
export default CommentButton;
In this code snippet, we are initializing the optimistic state. The optimisticComments
state variable holds the list of comments, including any optimistic updates (comments that appear immediately as the user adds them, before server confirmation). It starts as an empty array []
.
postComment
is a function you use to update optimisticComments
optimistically. It takes a new comment and adds it to the list of existing comments. The mutation function (comments, newComment) \=> [...comments, newComment]
specifies how to update the comments list when a new comment is added. It creates a new array containing all existing comments and the new comment.
In order to submit a comment optimistically, you’d first create an optimistic comment. optimisticNewComment
is a new comment object with a temporary ID, the text from the input field, and a status of sending
. This object represents the comment that the user just added.
Then, to achieve an optimistic update, you’d use postComment(optimisticNewComment)
, which updates the optimisticComments
list to include the new comment immediately. This gives the user instant feedback that their comment has been added.
Then, the try
block would attempt to send the new comment to the server using api.postComment(postId, newComment)
. If the server confirms the comment was added, the status of optimisticNewComment
is updated to sent
. The list of comments is updated to reflect this new status. If the server call fails, the catch
block removes the optimistic comment from optimisticComments
and sets an error message to inform the user that the comment could not be posted.
When we run our application and hit the Post Comment button, it looks like this: Note how it says Sending along with the comment — that’s exactly what the useOptimistic
Hook does. It posts the comment while still waiting for the network request to complete.
Optimistic UI vs. Traditional methods
By implementing the optimistic UI pattern, applications can mimic immediate responsiveness by updating user interfaces as soon as the user interacts, which is especially vital in seamless interaction environments like social media.
Traditional UI updates rely on server response times, subjecting users to long wait times and making the application feel sluggish. Optimistic UI transforms this experience by updating interfaces preemptively, showing users the expected outcomes of their actions instantly — like seeing a comment appear the moment it's clicked or a comment post immediately.
This method keeps users actively engaged and enhances their overall experience by ensuring a continuous and seamless interactive flow. This strategy is critical for maintaining user satisfaction in high-engagement platforms.
Optimistic UI in reliable environments
Optimistic UI excels in scenarios where the actions are nearly always successful, such as sending a message or preference updates within an app. In these contexts, server errors are exceptions rather than the rule, and even when they occur, they are typically non-disruptive to the overall user experience. This reliability positions optimistic UI as a key strategy for modern applications aimed at enhancing user retention and satisfaction.
When to use Optimistic UI:
- Social media platforms: Ensuring instant feedback for actions like posting comments, liking posts, and sharing content
- Ecommerce applications: Providing immediate updates when adding items to a cart, wish list, or during the checkout process
- Collaborative tools: Enhancing real-time collaboration by immediately showing changes (e.g., document edits, task updates)
- Messaging apps: Allowing users to see their messages in the conversation thread instantly after sending
- Booking systems: Updating available slots or bookings in real time to avoid conflicts and improve user satisfaction
Less suitable environments:
- Critical financial transactions: Where accuracy and confirmation are crucial, such as bank transfers or stock trades
- Medical applications: When dealing with sensitive health data or actions that require verified updates
- Systems with high latency and unreliable connections: Where the likelihood of conflicts or errors due to optimistic updates is high
- Legal document management: Where ensuring the accuracy and legality of every change is crucial
Managing errors and reversions with useOptimistic
While optimistic UI offers numerous benefits, it also presents challenges, notably in handling the unexpected — like server errors. React’s useOptimistic
Hook addresses this by enabling a smooth rollback to the UI’s previous state if an action fails.
For instance, if a “comment” on a social media post doesn't go through, the UI can automatically revert, removing the comment, and a concise error message informs the user of the hiccup. This approach not only maintains the integrity and transparency of the UI but also improves user trust by demonstrating the application’s reliability, even in the face of errors. Many modern applications use this approach to improve user interactions.
Conclusion
In this article, we walked through how React’s new useOptimistic
Hook can be used to make apps feel quicker and more responsive. We looked at how a "Like" button can show an immediate response using the example of a social media app.
The useOptimistic
Hook makes interactions with apps seamless and quick. Even though it might mean a little extra work to handle errors, like when we need to undo a like if something goes wrong, it's a good trade-off for a much better user experience and faster app performance.
Whether it’s chat messages, likes, or another async action, consider using useOptimistic
to create a delightful user experience and make sure to handle potential errors.
Happy coding!
Get set up with LogRocket's modern React error tracking in minutes:
- Visit https://logrocket.com/signup/ to get an app ID.
- Install LogRocket via NPM or script tag.
LogRocket.init()
must be called client-side, not server-side.
NPM:
$ npm i --save logrocket
// Code:
import LogRocket from 'logrocket';
LogRocket.init('app/id');
Script Tag:
Add to your HTML:
<script src="https://cdn.lr-ingest.com/LogRocket.min.js"></script>
<script>window.LogRocket && window.LogRocket.init('app/id');</script>
3.(Optional) Install plugins for deeper integrations with your stack:
- Redux middleware
- ngrx middleware
- Vuex plugin
Top comments (0)