Backbencher.dev

Mutations in React Query

Last updated on 19 Oct, 2021

Mutations in React Query handles data modification. We use queries to read server data. On the other hand, mutations helps us to modify data through creation, updation or deletion.

API For Testing

In order to try mutations, I have created two APIs. One is to return a list of cars and other one to add a new car.

When we do a GET request to /cars endpoint, we receive a list of cars.

[
  {
    "id": 1,
    "title": "Mercedes GLS",
    "description": "Big SUV from Mercedes"
  },
  {
    "id": 2,
    "title": "BMW 3 Series",
    "description": "Best sedan from BMW"
  }
]

We can add a new car to the list by sending a POST request to /cars with the new car object.

{
  "title": "Mercedes Maybach",
  "description": "Ultimate luxury"
}

Querying Data

As a first step, we create a Cars component that list all cars from the API.

function Cars() {
  const cars = useQuery("cars", async () => {
    const { data } = await axios.get("http://localhost:9000/cars");
    return data;
  });

  return (
    <>
      {cars.isLoading ? (
        <h1>Loading...</h1>
      ) : (
        <div>
          <h1>Cars</h1>

          {cars.data.map((car) => (
            <div key={car.id}>
              <h2>{car.title}</h2>
              {car.description}
            </div>
          ))}
        </div>
      )}
    </>
  );
}

Here, we are just using a normal useQuery invocation to get the list of cars from the API. Nothing related to mutations!

Creating an Item

We are going to add a new item to the server. We add a new form to the component to collect car details.

<form onSubmit={createCar}>
  <input type="text" onChange={(e) => setTitle(e.target.value)} />
  <input type="text" onChange={(e) => setDescription(e.target.value)} />
  <input type="submit" />
</form>

As per the above form, we call createCar function on form submit. Also, we are storing the form values in the state.

Now, createCar logic looks like below.

const createCar = (e) => {
  e.preventDefault();
  const newCar = {
    title,
    description,
  };
  axios.post("http://localhost:9000/cars", newCar);
};

As we can see, above code adds a new car to the list. But in the UI, we cannot see any change immediately. We need to either reload or refocus on window to view the change. So, how can we tell React Query to refresh the list immediately after content update?

We can make use of invalidateQueries() method. Let us add that method in the success handler of axios.

const createCar = (e) => {
  // ...
  axios.post("http://localhost:9000/cars", newCar).then(() => {    queryClient.invalidateQueries("cars");  });};

What is happening here is that, after the content is updated in the server, we are invalidating the query with the key "cars". That forces React Query to fetch the list of cars again.

Now wait! Everything looks perfect. What is the role of mutation now?

useMutation Hook

In the code so far, we did a happy path of creating a new car. Now we need to do additional enhancements as listed below.

  • Disable the submit button while the API request is running
  • Show Saved! text in the button after the new car is added
  • and so on..

If we are going with axios, we need to bring extra variables and flags to handle these situations. Mutations in React Query make things easy.

In order to use mutation, first import useMutation hook from React Query.

import { useMutation } from "react-query";

Using useMutation hook, we create a new mutation object. First argument of the hook is a callback function that returns a promise. This promise when resolved, calls the create car API. Second argument is an object that can contain predefined keys like onSuccess which is used to handle different situations like API error or success. We add the following code directly inside Cars component.

function Cars() {
  //...

  const mutation = useMutation(
    (newCar) => {
      return axios.post("http://localhost:9000/cars", newCar);
    },
    {
      onSuccess: () => {
        queryClient.invalidateQueries("cars");
      },
    }
  );

  //...
}

Next we need to use the mutation object. For that, we use mutate() method inside createCar function.

const createCar = (e) => {
  e.preventDefault();
  const newCar = {
    title,
    description,
  };
  mutation.mutate(newCar);
};

Now we implemented the same functionality of adding a new car from the previous section using mutation.

Using Mutation Properties

We are going to be more user friendly. We are going to show a Saved! text in the button, once mutation succeeds.

Right now the button in form takes the default Submit text. Lets place that text conditionally.

<input type="submit" value={mutation.isSuccess ? "Saved!" : "Submit"} />

We used the isSuccess property from the mutation object. You can play with the other properties and make your application more efficient and friendly.

--- ○ ---
Joby Joseph
Web Architect