Handling Mutations with TanStack Query

In TanStack Query, mutations are used for modifying server-side data, such as creating, updating, or deleting records. The useMutation hook simplifies the process of handling these operations and provides tools for managing loading states, errors, and cache updates.


This tutorial will guide you through:

  • Setting up mutations.
  • Updating cache after mutations.
  • Implementing optimistic updates.

Step 1: Setting Up the Query Client

Before working with mutations, initialize the QueryClient to manage queries and mutations in your app.


Install TanStack Query

npm install @tanstack/react-query

npm install @tanstack/react-query-devtools


Setup QueryClient

App.js

import React from 'react';

import { QueryClient, QueryClientProvider } from '@tanstack/react-query';

import { ReactQueryDevtools } from '@tanstack/react-query-devtools';

import { CreateUser } from './CreateUser';


const queryClient = new QueryClient();


function App() {

  return (

    <QueryClientProvider client={queryClient}>

      <h1>Handling Mutations with TanStack Query</h1>

      <CreateUser />

      <ReactQueryDevtools initialIsOpen={false} />

    </QueryClientProvider>

  );

}


export default App;


Step 2: Creating Data with Mutations

Use the useMutation hook to handle POST requests and modify server-side data.


Example: Creating a User

CreateUser.js

import React, { useState } from 'react';

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


const createUser = async (newUser) => {

  const response = await fetch('https://jsonplaceholder.typicode.com/users', {

    method: 'POST',

    headers: { 'Content-Type': 'application/json' },

    body: JSON.stringify(newUser),

  });

  if (!response.ok) throw new Error('Failed to create user');

  return response.json();

};


export function CreateUser() {

  const [name, setName] = useState('');

  const queryClient = useQueryClient();


  const mutation = useMutation(createUser, {

    onSuccess: () => {

      // Invalidate the 'users' query to fetch the updated data

      queryClient.invalidateQueries(['users']);

    },

  });


  const handleSubmit = (e) => {

    e.preventDefault();

    mutation.mutate({ name });

    setName('');

  };


  return (

    <form onSubmit={handleSubmit}>

      <input

        type="text"

        value={name}

        onChange={(e) => setName(e.target.value)}

        placeholder="Enter user name"

        required

      />

      <button type="submit" disabled={mutation.isLoading}>

        {mutation.isLoading ? 'Creating...' : 'Create User'}

      </button>

      {mutation.isError && <p>Error: {mutation.error.message}</p>}

    </form>

  );

}


Step 3: Updating Data with Mutations

To update server-side data, use the useMutation hook with PUT or PATCH requests.


Example: Updating User Data

UpdateUser.js

import React, { useState } from 'react';

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


const updateUser = async ({ id, name }) => {

  const response = await fetch(`https://jsonplaceholder.typicode.com/users/${id}`, {

    method: 'PUT',

    headers: { 'Content-Type': 'application/json' },

    body: JSON.stringify({ name }),

  });

  if (!response.ok) throw new Error('Failed to update user');

  return response.json();

};


export function UpdateUser({ id, currentName }) {

  const [name, setName] = useState(currentName);

  const queryClient = useQueryClient();


  const mutation = useMutation(updateUser, {

    onSuccess: () => {

      queryClient.invalidateQueries(['users']); // Refresh the 'users' query

    },

  });


  const handleSubmit = (e) => {

    e.preventDefault();

    mutation.mutate({ id, name });

  };


  return (

    <form onSubmit={handleSubmit}>

      <input

        type="text"

        value={name}

        onChange={(e) => setName(e.target.value)}

        required

      />

      <button type="submit" disabled={mutation.isLoading}>

        {mutation.isLoading ? 'Updating...' : 'Update User'}

      </button>

      {mutation.isError && <p>Error: {mutation.error.message}</p>}

    </form>

  );

}


Step 4: Deleting Data with Mutations

Handle DELETE requests to remove records from the server.


Example: Deleting a User

DeleteUser.js

import React from 'react';

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


const deleteUser = async (id) => {

  const response = await fetch(`https://jsonplaceholder.typicode.com/users/${id}`, {

    method: 'DELETE',

  });

  if (!response.ok) throw new Error('Failed to delete user');

  return id;

};


export function DeleteUser({ id }) {

  const queryClient = useQueryClient();


  const mutation = useMutation(deleteUser, {

    onSuccess: () => {

      queryClient.invalidateQueries(['users']); // Refresh the 'users' query

    },

  });


  return (

    <button onClick={() => mutation.mutate(id)} disabled={mutation.isLoading}>

      {mutation.isLoading ? 'Deleting...' : 'Delete User'}

    </button>

  );

}


Step 5: Optimistic Updates

Optimistic updates allow you to update the UI immediately, before the mutation completes, for better user experience.


Example: Optimistic Update for Deleting a User

DeleteUser.js

const mutation = useMutation(deleteUser, {

  onMutate: async (id) => {

    await queryClient.cancelQueries(['users']); // Cancel ongoing queries


    const previousUsers = queryClient.getQueryData(['users']); // Snapshot current cache


    // Optimistically update cache

    queryClient.setQueryData(['users'], (old) =>

      old.filter((user) => user.id !== id)

    );


    return { previousUsers }; // Return context

  },

  onError: (error, id, context) => {

    queryClient.setQueryData(['users'], context.previousUsers); // Rollback on error

  },

  onSettled: () => {

    queryClient.invalidateQueries(['users']); // Refetch users

  },

});


Step 6: Putting It All Together

Combine the Create, Update, and Delete components in a single application.


App.js

import React from 'react';

import { QueryClient, QueryClientProvider } from '@tanstack/react-query';

import { CreateUser } from './CreateUser';

import { UpdateUser } from './UpdateUser';

import { DeleteUser } from './DeleteUser';


const queryClient = new QueryClient();


function App() {

  return (

    <QueryClientProvider client={queryClient}>

      <h1>TanStack Query: Handling Mutations</h1>

      <CreateUser />

      {/* Add UpdateUser and DeleteUser components here */}

    </QueryClientProvider>

  );

}


export default App;


Best Practices

  • Invalidate Queries: Always invalidate or update the cache after successful mutations.
  • Error Handling: Use onError to manage mutation errors.
  • Optimistic Updates: Improve user experience by reflecting changes instantly.
  • Separation of Concerns: Keep fetching (queries) and updating (mutations) logic separate.


In this tutorial, you learned how to:

  • Handle Create, Update, and Delete operations using useMutation.
  • Invalidate or update cached data after mutations.
  • Implement optimistic updates for better UI responsiveness.

These techniques make TanStack Query a robust choice for managing server-side state in modern applications.  Hope this is helpful, and I apologize if there are any inaccuracies in the information provided.

Comments

Popular posts from this blog

Integrating PHP with Message Queues RabbitMQ Kafka

FastAPI and UVLoop: The Perfect Pair for Asynchronous API Development

Konfigurasi dan Instalasi PostgreSQL Secara Lengkap di Windows Linux dan MacOS