Course
Optimistic Update
Next.js Mastery: From Fundamentals to Full-Stack
Unlock the power of Next.js with this comprehensive course! Starting with the basics, you’ll learn essential skills such as routing, data fetching, and styling. Progress through practical projects, including building your own React Notes app, to gain hands-on experience. Dive into the source code to understand the inner workings and core principles of Next.js. Perfect for developers with a basic knowledge of React and Node.js, this course ensures you’ll be ready to create high-performance full-stack applications efficiently. Join us and master Next.js, backed by industry experts and a supportive learning community.
Optimistic Updates
Optimistic updates are a powerful UX strategy where the UI is updated immediately upon a user action, before confirming the success of the underlying data operation. If the operation fails, the UI may be rolled back or adjusted to reflect the failure.
1. useOptimistic Hook
React's
useOptimistic
hook is designed to facilitate optimistic updates. The basic usage involves setting an initial state and providing an update function that merges new values into the current state optimistically. Here’s a basic structure:import { useOptimistic } from 'react';
function AppContainer() { const [optimisticState, addOptimistic] = useOptimistic( state, (currentState, optimisticValue) => { // Merge and return new state with the optimistic value return [...currentState, optimisticValue]; } );}
When used in conjunction with Server Actions, it looks like this:
'use client'
import { useOptimistic } from 'react'import { send } from './actions'
export function Thread({ messages }) { const [optimisticMessages, addOptimisticMessage] = useOptimistic( messages, (state, newMessage) => [...state, { message: newMessage }] )
return ( <div> {optimisticMessages.map((m, i) => ( <div key={i}>{m.message}</div> ))} <form action={async (formData) => { const message = formData.get('message') addOptimisticMessage(message) await send(message) }} > <input type="text" name="message" /> <button type="submit">Send</button> </form> </div> )}
2. Example: Building a ToDo List with Optimistic Updates
To illustrate optimistic updates, let's build a simple ToDo list application using Next.js,
useOptimistic
, and Server Actions. Here’s how the project is structured:app └─ form4 ├─ actions.js ├─ form.js └─ page.js
app/form4/page.js
import { findToDos } from './actions';import Form from './form';
export default async function Page() { const todos = await findToDos(); return ( <div className="max-w-md mx-auto mt-8 p-6 bg-white rounded-lg shadow-md"> <h1 className="text-2xl font-bold mb-4">To-do List</h1> <Form todos={todos} /> </div> );}
app/form4/form.js
'use client'
import { useOptimistic } from 'react'import { useFormState } from 'react-dom'import { createToDo } from './actions';
export default function Form({ todos }) { const [state, sendFormAction] = useFormState(createToDo, { message: '' });
const [optimistiToDos, addOptimisticTodo] = useOptimistic( todos.map((i) => ({ text: i })), (state, newTodo) => [ ...state, { text: newTodo, sending: true } ] );
async function formAction(formData) { addOptimisticTodo(formData.get("todo")); await sendFormAction(formData); }
return ( <> <form action={formAction} className="mb-4"> <div className="flex space-x-2"> <input type="text" name="todo" className="flex-grow px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500" placeholder="Input to-do item.." /> <button type="submit" className="bg-black text-white px-4 py-2 rounded-md hover:bg-blue-600 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-opacity-50" > Add </button> </div> <p aria-live="polite" className="sr-only"> {state?.message} </p> </form> <ul className="space-y-2"> {optimistiToDos.map(({ text, sending }, i) => ( <li key={i} className="bg-gray-100 p-2 rounded-md flex justify-between items-center"> <span>{text}</span> {!!sending && <small className="text-gray-500 italic">Sending...</small>} </li> ))} </ul> </> );}
app/form4/actions.js
'use server'
import { revalidatePath } from "next/cache";
const sleep = ms => new Promise(r => setTimeout(r, ms));
let data = ['Reading', 'Writing', 'Meditation']
export async function findToDos() { return data;}
export async function createToDo(prevState, formData) { await sleep(2500); const todo = formData.get('todo'); data.push(todo); revalidatePath("/form4"); return { message: `add ${todo} success!` };}
The interactive effect is as follows:
The app immediately updates the ToDo list in the UI when a new item is added, showing it as "Sending..." while the Server Action processes the request. If successful, the "Sending..." label is removed.
Handling Common Issues
Dealing with Cookies:
'use server' import { cookies } from 'next/headers' export async function exampleAction() { // Get cookie const value = cookies().get('name')?.value // Set cookie cookies().set('name', 'Delba') // Delete cookie cookies().delete('name')}
Redirecting After Actions:
'use server' import { redirect } from 'next/navigation'import { revalidateTag } from 'next/cache' export async function createPost(id) { try { // ... } catch (error) { // ... } revalidateTag('posts') // Update cached posts redirect(`/post/${id}`) // Navigate to the new post page}
Conclusion
Optimistic updates provide a seamless user experience by making the UI immediately responsive to user actions. While powerful, they require careful handling of potential errors and state rollbacks. Using React's
useOptimistic
hook alongside Server Actions in Next.js enables you to implement optimistic UI updates effectively.