Skip to main content
Chapter 4 Server Actions and Forms

Optimistic Updates with useOptimistic

18 min read Lesson 16 / 28

Instant UI with Optimistic Updates

useOptimistic shows an optimistic (assumed successful) UI update while the Server Action is running. If the action fails, the UI reverts. This makes interactions feel instant even with network latency.

Todo List with Optimistic Toggle

// app/todos/todo-list.tsx
'use client';

import { useOptimistic } from 'react';
import { toggleTodo } from '@/actions/todos';

interface Todo {
    id: string;
    text: string;
    completed: boolean;
}

export function TodoList({ todos }: { todos: Todo[] }) {
    const [optimisticTodos, addOptimisticTodo] = useOptimistic(
        todos,
        (currentTodos, toggledId: string) =>
            currentTodos.map(todo =>
                todo.id === toggledId
                    ? { ...todo, completed: !todo.completed }
                    : todo
            )
    );

    async function handleToggle(id: string) {
        addOptimisticTodo(id); // Update UI immediately
        await toggleTodo(id);  // Then update server
    }

    return (
        <ul className="space-y-2">
            {optimisticTodos.map(todo => (
                <li
                    key={todo.id}
                    className="flex items-center gap-3 p-3 border rounded-lg"
                >
                    <button
                        onClick={() => handleToggle(todo.id)}
                        className={`w-5 h-5 rounded border-2 flex items-center justify-center
                            ${todo.completed
                                ? 'bg-green-500 border-green-500'
                                : 'border-gray-300'}`}
                    >
                        {todo.completed && (
                            <svg className="w-3 h-3 text-white" fill="currentColor" viewBox="0 0 12 12">
                                <path d="M10 3L5 8.5 2 5.5" stroke="white" strokeWidth="2" fill="none"/>
                            </svg>
                        )}
                    </button>
                    <span className={todo.completed ? 'line-through text-gray-400' : ''}>
                        {todo.text}
                    </span>
                </li>
            ))}
        </ul>
    );
}

Like Button with Optimistic Count

'use client';

import { useOptimistic, useTransition } from 'react';
import { likePost } from '@/actions/posts';

export function LikeButton({
    postId,
    initialCount,
    initialLiked,
}: {
    postId: string;
    initialCount: number;
    initialLiked: boolean;
}) {
    const [, startTransition] = useTransition();
    const [optimisticState, setOptimistic] = useOptimistic(
        { count: initialCount, liked: initialLiked },
        (current, newLiked: boolean) => ({
            count: newLiked ? current.count + 1 : current.count - 1,
            liked: newLiked,
        })
    );

    function handleLike() {
        startTransition(async () => {
            setOptimistic(!optimisticState.liked);
            await likePost(postId);
        });
    }

    return (
        <button onClick={handleLike} className="flex items-center gap-2">
            <span>{optimisticState.liked ? '❤️' : '🤍'}</span>
            <span>{optimisticState.count}</span>
        </button>
    );
}

Optimistic updates bring native-app responsiveness to web applications — users see immediate feedback instead of waiting for network roundtrips.