Mastering React’s useOptimistic Hook: A Comprehensive Guide with Examples

Love Trivedi
ZestGeek
Published in
4 min readAug 7, 2024

--

React’s evolving hooks API continually introduces new ways to manage state and side effects efficiently. One of the newer hooks, useOptimistic, offers a straightforward way to handle optimistic UI updates. This article will explore useOptimistic, explaining its use cases and providing practical coding examples to demonstrate its effectiveness.

What is useOptimistic?

The useOptimistic hook allows you to make optimistic updates to your UI. This means you can update the user interface immediately, assuming a successful result of an asynchronous operation, and then revert it if the operation fails. This approach improves the user experience by making the UI feel more responsive.

Syntax

const [optimisticState, applyOptimisticUpdate] = useOptimistic(initialState, updaterFunction);
  • initialState: The initial state before any optimistic updates.
  • updaterFunction: A function that defines how the state should be updated optimistically.

When to Use useOptimistic

  • Instant Feedback: When you want to provide immediate feedback to users while an operation is in progress.
  • Network Latency: When dealing with operations that might have noticeable network latency, making the UI appear more responsive.

Real-Life Coding Examples

Example 1: Adding an Item to a List

Consider a to-do list application where you want to add a new item optimistically:

import React, { useState } from 'react';
import { useOptimistic } from 'react';

function TodoList() {
const [todos, setTodos] = useState([]);
const [optimisticTodos, addOptimisticTodo] = useOptimistic(todos, (todos, newTodo) => [...todos, newTodo]);

const handleAddTodo = async (newTodo) => {
addOptimisticTodo(newTodo);
try {
// Simulate an API call
await fakeApiCallToAddTodo(newTodo);
} catch (error) {
// Revert optimistic update if API call fails
setTodos((prevTodos) => prevTodos.filter((todo) => todo !== newTodo));
}
};

return (
<div>
<ul>
{optimisticTodos.map((todo, index) => (
<li key={index}>{todo}</li>
))}
</ul>
<button onClick={() => handleAddTodo(`New Todo ${Date.now()}`)}>Add Todo</button>
</div>
);
}

const fakeApiCallToAddTodo = (todo) => new Promise((resolve, reject) => {
setTimeout(() => {
Math.random() > 0.5 ? resolve() : reject();
}, 1000);
});

export default TodoList;

In this example, useOptimistic is used to immediately add a new to-do item to the list. If the simulated API call fails, the item is removed from the list.

Example 2: Deleting an Item from a List

Here’s how you can optimistically delete an item from a list:

import React, { useState } from 'react';
import { useOptimistic } from 'react';

function TodoList() {
const [todos, setTodos] = useState(['Todo 1', 'Todo 2', 'Todo 3']);
const [optimisticTodos, removeOptimisticTodo] = useOptimistic(todos, (todos, todoToRemove) => todos.filter(todo => todo !== todoToRemove));

const handleDeleteTodo = async (todoToRemove) => {
removeOptimisticTodo(todoToRemove);
try {
// Simulate an API call
await fakeApiCallToDeleteTodo(todoToRemove);
} catch (error) {
// Revert optimistic update if API call fails
setTodos((prevTodos) => [...prevTodos, todoToRemove]);
}
};

return (
<div>
<ul>
{optimisticTodos.map((todo, index) => (
<li key={index}>
{todo}
<button onClick={() => handleDeleteTodo(todo)}>Delete</button>
</li>
))}
</ul>
</div>
);
}

const fakeApiCallToDeleteTodo = (todo) => new Promise((resolve, reject) => {
setTimeout(() => {
Math.random() > 0.5 ? resolve() : reject();
}, 1000);
});

export default TodoList;

In this example, useOptimistic allows immediate removal of an item from the list. If the deletion API call fails, the item is restored.

Example 3: Updating an Item in a List

Optimistically updating an item in a list:

import React, { useState } from 'react';
import { useOptimistic } from 'react';

function TodoList() {
const [todos, setTodos] = useState(['Todo 1', 'Todo 2', 'Todo 3']);
const [optimisticTodos, updateOptimisticTodo] = useOptimistic(todos, (todos, updatedTodo) =>
todos.map(todo => todo === updatedTodo.old ? updatedTodo.new : todo)
);

const handleUpdateTodo = async (oldTodo, newTodo) => {
updateOptimisticTodo({ old: oldTodo, new: newTodo });
try {
// Simulate an API call
await fakeApiCallToUpdateTodo();
} catch (error) {
// Revert optimistic update if API call fails
setTodos((prevTodos) => prevTodos.map(todo => todo === newTodo ? oldTodo : todo));
}
};

return (
<div>
<ul>
{optimisticTodos.map((todo, index) => (
<li key={index}>
{todo}
<button onClick={() => handleUpdateTodo(todo, `Updated ${todo}`)}>Update</button>
</li>
))}
</ul>
</div>
);
}

const fakeApiCallToUpdateTodo = () => new Promise((resolve, reject) => {
setTimeout(() => {
Math.random() > 0.5 ? resolve() : reject();
}, 1000);
});

export default TodoList;

Here, useOptimistic is used to immediately update an item in the list. If the API call fails, the old value is restored.

Conclusion

The useOptimistic hook in React offers a powerful way to handle optimistic UI updates, enhancing the user experience by providing immediate feedback. By using useOptimistic, you can handle state changes optimistically and revert them if an operation fails, making your applications feel more responsive and dynamic.

These real-life coding examples illustrate how useOptimistic can be used to manage adding, deleting, and updating items in a list. As you incorporate useOptimistic into your projects, you’ll find it an invaluable tool for improving the responsiveness and reliability of your React applications.

--

--

Love Trivedi
ZestGeek

Full Stack Developer | Problem Solver | Knowledge Share, 🚀 Expertise: JavaScript enthusiast specializing in ReactJS, Angular, and Node.js.