React useOptimistic — Optimistic UI Updates

Sanjeev SharmaSanjeev Sharma
2 min read

Advertisement

React useOptimistic — Optimistic UI Updates

Optimistic updates create instant user feedback while operations complete in the background.

Basic Usage

'use client'

import { useOptimistic } from 'react'

export function TodoList() {
  const [todos, setTodos] = useState([
    { id: 1, text: 'Learn React' }
  ])

  const [optimisticTodos, addOptimisticTodo] = useOptimistic(todos)

  async function handleAddTodo(formData) {
    const newTodo = { id: Date.now(), text: formData.get('todo') }

    // Update UI immediately
    addOptimisticTodo(newTodo)

    // Server operation
    const result = await fetch('/api/todos', {
      method: 'POST',
      body: JSON.stringify(newTodo)
    })

    if (!result.ok) {
      setTodos(todos) // Revert on error
    }
  }

  return (
    <form action={handleAddTodo}>
      {optimisticTodos.map(todo => (
        <div key={todo.id}>{todo.text}</div>
      ))}
      <input name="todo" />
      <button type="submit">Add</button>
    </form>
  )
}

With useFormStatus

'use client'

import { useOptimistic } from 'react'
import { useFormStatus } from 'react-dom'
import { addComment } from '@/app/actions'

function CommentForm({ postId, onAddComment }) {
  const { pending } = useFormStatus()

  return (
    <form action={onAddComment}>
      <textarea name="text" disabled={pending} />
      <button disabled={pending}>
        {pending ? 'Posting...' : 'Post'}
      </button>
    </form>
  )
}

export function CommentSection({ postId, initialComments }) {
  const [optimisticComments, addOptimisticComment] = useOptimistic(initialComments)

  async function handleAddComment(formData) {
    const newComment = {
      id: Math.random(),
      text: formData.get('text')
    }

    addOptimisticComment(newComment)

    const result = await addComment(postId, formData.get('text'))

    if (!result.success) {
      // Revert optimistic update
    }
  }

  return (
    <div>
      {optimisticComments.map(comment => (
        <div key={comment.id}>{comment.text}</div>
      ))}
      <CommentForm postId={postId} onAddComment={handleAddComment} />
    </div>
  )
}

Real-World: Shopping Cart

'use client'

import { useOptimistic } from 'react'
import { updateCart } from '@/app/actions'

export function CartItem({ item }) {
  const [optimisticQuantity, updateOptimisticQuantity] = useOptimistic(item.quantity)

  async function handleQuantityChange(newQuantity) {
    updateOptimisticQuantity(newQuantity)

    const result = await updateCart(item.id, newQuantity)

    if (!result.success) {
      updateOptimisticQuantity(item.quantity) // Revert
    }
  }

  return (
    <div className="flex items-center gap-4">
      <span>{item.name}</span>
      <input
        type="number"
        value={optimisticQuantity}
        onChange={(e) => handleQuantityChange(Number(e.target.value))}
      />
      <span>${item.price * optimisticQuantity}</span>
    </div>
  )
}

FAQ

Q: What happens if the server operation fails? A: You must revert the optimistic update manually.

Q: Should I use optimistic updates everywhere? A: Only for operations where instant feedback matters (likes, quantities).

Q: How do I handle errors? A: Check the response and revert if needed using the setter.


Optimistic updates dramatically improve perceived performance and user experience.

Advertisement

Sanjeev Sharma

Written by

Sanjeev Sharma

Full Stack Engineer · E-mopro