Enable Dark Mode!
how-to-implement-optimistic-updates-in-react-with-tanstack-query.jpg
By: Alan Joy

How to Implement Optimistic Updates in React with TanStack Query

Technical odoo React

We have all noticed in some applications that when clicking the like button or increasing the count, the changes are updated instantly without causing any loading or waiting for the request to complete. These actions are called optimistic updates. Instead of waiting for the api to respond, the application assumes that the request will succeed and updates the UI immediately. If something goes wrong or the API fails, it can simply roll back the change.

Optimistic updates make our applications feel much faster and more responsive, and these are a common feature in modern applications. In this blog, we can learn how to implement optimistic updates in React using TanStack Query.

What Are Optimistic Updates?

Normally, when a user performs an action in an app, its flow will be like this: User clicks a button - application sends a request to the server - application waits for response - once the response arrives, the UI updates.

This approach works, but it can make the application feel slower. In the case of optimistic updates, the flow changes: the user clicks a button - the UI updates immediately - a request is sent in the background - if the request succeeds, nothing changes - if the request fails, UI reverts to the previous state. In this case, the ui updates immediately and the user gets instant feedback for the action.

Setting Up the Project

First, create a React app using Vite and install the TanStack Query package.

npm create vite@latest
npm install
npm install @tanstack/react-query

Now, we need to setup query client. Open your main.jsx or main.tsx file. The QueryClientProvider gives the app access to the TanStack Query features.

import React from "react";
import ReactDOM from "react-dom/client";
import App from "./App";
import {
  QueryClient,
  QueryClientProvider,
} from "@tanstack/react-query";
const queryClient = new QueryClient();
ReactDOM.createRoot(document.getElementById("root")).render(
  <QueryClientProvider client={queryClient}>
    <App />
  </QueryClientProvider>
);

Fetching the Todo List

First, let's create a function to fetch the list of todos. Here I am using the demo REST API.

const fetchTodos = async () => {
 const response = await fetch(
   "https://jsonplaceholder.typicode.com/todos?_limit=10"
 );
 if (!response.ok) {
   throw new Error("Failed to fetch todos");
 }
 return response.json();
};
const { data: todos } = useQuery({
 queryKey: ["todos"],
 queryFn: fetchTodos,
});

Creating a Mutation

Now, we want to create a mutation function to mark a todo as complete.

const updateTodo = async (todo) => {
 const response = await fetch(
   `https://jsonplaceholder.typicode.com/todos/${todo.id}`,
   {
     method: "PATCH",
     headers: {
       "Content-Type": "application/json",
     },
     body: JSON.stringify(todo),
   }
 );
 if (!response.ok) {
   throw new Error("Failed to update todo");
 }
 return response.json();
};
const mutation = useMutation({
 mutationFn: updateTodo,
});

Step 1: Update the Cache Immediately

The useMutation hook has an onMutate callback; this callback runs before the mutation function executes. This is where we will add the optimistic update logic.

const mutation = useMutation({
 mutationFn: updateTodo,
 onMutate: async (updatedTodo) => {
   // Stop any outgoing refetches
   await queryClient.cancelQueries({
     queryKey: ["todos"],
   });
   // Save current cache
   const previousTodos = queryClient.getQueryData(["todos"]);
   // Update cache immediately
   queryClient.setQueryData(["todos"], (old = []) =>
     old.map((todo) =>
       todo.id === updatedTodo.id
         ? { ...todo, completed: true }
         : todo
     )
   );
   // Returned value becomes "context"
   return { previousTodos };
 },
});

Step 2: Roll Back if Something Fails

It is recommended to set up the onError callback to handle the rollback of the update if the API request failed for any reason. If the mutation fails, we will simply restore the data; the user sees the original state again.

onError: (error, variables, context) => {
     queryClient.setQueryData(
       ["todos"],
       context?.previousTodos
     );
   },

Step 3: Refresh the Data

Even if the api request succeeds, it is a good idea to sync data with the server. We can use the onSettled callback for this case.

onSettled: () => {
     queryClient.invalidateQueries({
       queryKey: ["todos"],
     });
   },

Full code

Here, you can see the complete optimistic mutation code

import {
 useMutation,
 useQuery,
 useQueryClient,
} from "@tanstack/react-query";
// Fetch all todos
const fetchTodos = async () => {
 const response = await fetch(
   "https://jsonplaceholder.typicode.com/todos?_limit=10"
 );
 if (!response.ok) {
   throw new Error("Failed to fetch todos");
 }
 return response.json();
};
// Update a todo
const updateTodo = async (todo) => {
 const response = await fetch(
   `https://jsonplaceholder.typicode.com/todos/${todo.id}`,
   {
     method: "PATCH",
     headers: {
       "Content-Type": "application/json",
     },
     body: JSON.stringify(todo),
   }
 );
 if (!response.ok) {
   throw new Error("Failed to update todo");
 }
 return response.json();
};
export default function TodoList() {
 const queryClient = useQueryClient();
 // Fetch todos
 const {
   data: todos = [],
   isPending,
   error,
 } = useQuery({
   queryKey: ["todos"],
   queryFn: fetchTodos,
 });
 // Optimistic mutation
 const mutation = useMutation({
   mutationFn: updateTodo,
   // Runs before the request
   onMutate: async (updatedTodo) => {
     await queryClient.cancelQueries({
       queryKey: ["todos"],
     });
     const previousTodos =
       queryClient.getQueryData(["todos"]);
     queryClient.setQueryData(["todos"], (old = []) =>
       old.map((todo) =>
         todo.id === updatedTodo.id
           ? {
               ...todo,
               completed: updatedTodo.completed,
             }
           : todo
       )
     );
     // Returned value becomes "context"
     return { previousTodos };
   },
   // Rollback if request fails
   onError: (error, variables, context) => {
     queryClient.setQueryData(
       ["todos"],
       context?.previousTodos
     );
   },
   // Sync with server
   onSettled: () => {
     queryClient.invalidateQueries({
       queryKey: ["todos"],
     });
   },
 });
 if (isPending) {
   return <p>Loading...</p>;
 }
 if (error) {
   return <p>Something went wrong.</p>;
 }
 return (
   <div>
     <h2>Todo List</h2>
     {todos.map((todo) => (
       <div
         key={todo.id}
         style={{
           display: "flex",
           gap: "10px",
           marginBottom: "10px",
         }}
       >
         <span
           style={{
             textDecoration: todo.completed
               ? "line-through"
               : "none",
           }}
         >
           {todo.title}
         </span>
         <button
           onClick={() =>
             mutation.mutate({
               ...todo,
               completed: !todo.completed,
             })
           }
         >
           Toggle
         </button>
       </div>
     ))}
   </div>
 );
}

Optimistic updates are one of the easiest ways to make a React application faster. Instead of the user waiting for the server response, the user sees the updates or changes instantly; this creates a smoother and more responsive experience. TanStack Query makes this process more straightforward.

To read more about How to Fetch Paginated Data with TanStack Query in React, refer to our blog How to Fetch Paginated Data with TanStack Query in React.


Frequently Asked Questions

What are optimistic updates in TanStack Query?

Optimistic updates immediately update the UI before the server confirms the change, making applications feel faster.

Which hook is used for optimistic updates in TanStack Query?

Optimistic updates are implemented using the useMutation hook along with callbacks like onMutate, onError, and onSettled.

When should I use optimistic updates?

Use optimistic updates for actions that are likely to succeed, such as updating todos, liking posts, or changing user preferences.

If you need any assistance in odoo, we are online, please chat with us.



0
Comments



Leave a comment



WhatsApp