DEV Community

Champ of Greatness
Champ of Greatness

Posted on

Optimistic Concurrency in React Before useOptimistic

Optimistic concurrency is a powerful technique that enhances user experience by providing instant feedback before an operation is confirmed by the server. Before React 19 introduced useOptimistic, developers had to rely on various workarounds to achieve this behavior.

In this article, we will explore the most common approaches used in older versions of React to handle optimistic updates, their advantages, and their limitations.

πŸ” What is Optimistic Concurrency?

Optimistic concurrency assumes that an operation will succeed and updates the UI immediately without waiting for server confirmation. If the operation fails, the UI is rolled back to the previous state.

πŸš€ Approaches Before useOptimistic

1️⃣ Using useState for Temporary UI Updates

One of the simplest ways to implement optimistic updates in React is by using useState. Here’s how it works:

Example: Optimistic Update with useState

import React, { useState } from 'react';

function TodoList() {
  const [todos, setTodos] = useState([
    { id: 1, text: 'Learn React' },
    { id: 2, text: 'Build a Project' },
  ]);

  function handleAddTodo() {
    const newTodo = { id: Date.now(), text: 'New Todo' };

    // Optimistically update UI
    setTodos((prev) => [...prev, newTodo]);

    // Simulate API call
    fetch('/api/todos', {
      method: 'POST',
      body: JSON.stringify(newTodo),
    }).catch(() => {
      // Rollback on error
      setTodos((prev) => prev.filter((todo) => todo.id !== newTodo.id));
    });
  }

  return (
    <div>
      <button onClick={handleAddTodo}>Add Todo</button>
      <ul>
        {todos.map((todo) => (
          <li key={todo.id}>{todo.text}</li>
        ))}
      </ul>
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

βœ… Pros:

Simple and easy to implement

Provides immediate feedback to the user

❌ Cons:

Manual rollback is required if the API call fails

Causes unnecessary re-renders when state updates

Difficult to scale for complex state management

2️⃣ Using React Query’s onMutate

For applications that rely on external APIs, React Query was a popular choice before useOptimistic. React Query provides a way to optimistically update the UI and roll back changes on failure.

Example: React Query Optimistic Update

import { useMutation, useQueryClient } from '@tanstack/react-query';
import axios from 'axios';

function useAddTodo() {
  const queryClient = useQueryClient();

  return useMutation({
    mutationFn: (newTodo) => axios.post('/api/todos', newTodo),
    onMutate: async (newTodo) => {
      // Cancel any outgoing queries
      await queryClient.cancelQueries(['todos']);

      // Snapshot of previous todos
      const previousTodos = queryClient.getQueryData(['todos']);

      // Optimistically update UI
      queryClient.setQueryData(['todos'], (oldTodos) => [...oldTodos, newTodo]);

      return { previousTodos };
    },
    onError: (_, __, context) => {
      // Rollback to previous state on error
      queryClient.setQueryData(['todos'], context.previousTodos);
    },
  });
}
Enter fullscreen mode Exit fullscreen mode

βœ… Pros:

Automatically manages API state

Reduces unnecessary API calls

Built-in rollback mechanism

❌ Cons:

Requires an external library

More complex to set up

Still involves direct mutation of the cache

3️⃣ Using Redux for Optimistic Updates

Before React Query became popular, Redux was widely used to manage global state. Optimistic updates in Redux involved updating the store immediately and dispatching a rollback action if the API call failed.

Example: Optimistic Update with Redux

import { createSlice, configureStore } from '@reduxjs/toolkit';

const todosSlice = createSlice({
  name: 'todos',
  initialState: [],
  reducers: {
    addTodoOptimistic: (state, action) => {
      state.push(action.payload);
    },
    rollbackTodo: (state, action) => {
      return state.filter(todo => todo.id !== action.payload.id);
    },
  },
});

const store = configureStore({ reducer: { todos: todosSlice.reducer } });

// Simulate API call in a component
function handleAddTodo() {
  const newTodo = { id: Date.now(), text: 'New Todo' };
  store.dispatch(todosSlice.actions.addTodoOptimistic(newTodo));

  fetch('/api/todos', {
    method: 'POST',
    body: JSON.stringify(newTodo),
  }).catch(() => {
    store.dispatch(todosSlice.actions.rollbackTodo(newTodo));
  });
}
Enter fullscreen mode Exit fullscreen mode

βœ… Pros:

Works well for complex global state management

Redux DevTools make debugging easier

❌ Cons:

Requires boilerplate code

Overhead for simple state management

🎯 How useOptimistic Improves This

React 19’s useOptimistic provides a much cleaner and simpler way to handle optimistic updates. Instead of modifying state directly, useOptimistic allows you to derive a temporary optimistic state without affecting the actual state.

Example: useOptimistic (React 19)

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

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

  const [optimisticTodos, addOptimisticTodo] = useOptimistic(
    todos,
    (state, newTodo) => [...state, newTodo]
  );

  function handleAddTodo() {
    const newTodo = { id: Date.now(), text: 'New Todo' };

    addOptimisticTodo(newTodo);
    setTodos((prev) => [...prev, newTodo]);
  }

  return (
    <ul>
      {optimisticTodos.map((todo) => (
        <li key={todo.id}>{todo.text}</li>
      ))}
    </ul>
  );
}

Enter fullscreen mode Exit fullscreen mode

βœ… Benefits of useOptimistic:

No direct state mutation

UI updates instantly without re-rendering

No manual rollback needed

πŸ“Œ Conclusion

Before React 19, optimistic concurrency was achieved through useState, React Query, or Redux. Each method had its drawbacks, such as manual rollbacks, direct state mutations, and additional dependencies. With useOptimistic, React now provides a built-in, cleaner, and more efficient way to handle optimistic updates, making UI interactions smoother than ever.

Top comments (0)