Carl Rippon

Building SPAs

Carl Rippon
BlogBooks / CoursesAbout
This site uses cookies. Click here to find out more

Update Requests with React Query

August 28, 2021
reacttypescript

This is another post in a series of posts using React Query with TypeScript.

Previous posts:

The post covers how to use React Query with requests that make data updates. We will build a form that when submitted will make a PUT HTTP request.

Update reqeusts

A person form

Our form is as follows:

export function App() {
  return (
    <div>
      <form>
        <p>ID:</p>
        <fieldset>
          <div>
            <label htmlFor="firstName">First name</label>
            <input
              type="text"
              id="firstName"
              name="firstName"
            />
          </div>
          <div>
            <label htmlFor="lastName">Last name</label>
            <input
              type="text"
              id="lastName"
              name="lastName"
            />
          </div>
        </fieldset>
        <button type="submit">Save</button>
      </form>
    </div>
  );
}

The form contains fields to capture a first name and last name.

We want the form to contain existing data so that the user can make any necessary changes. We will use React Query to manage the request that fetches the existing data. Here’s the query:

export function App() {
  const { status: queryStatus, error: queryError, data } = useQuery<
    Person,
    Error
  >(["person", { id: 1 }], getPerson);
  
  if (queryStatus === "loading") {
    return <div>...</div>;
  }
  if (queryStatus === "error") {
    return <div>{queryError!.message}</div>;
  }
  ...
}

We alias the state variables so that they won’t clash with state variables that will come into play later. The type for a person is:

type Person = {
  id: number;
  firstName: string;
  lastName: string;
};

We are requesting person with an id of 1 from the fetching function getPerson. Here is the fetching function:

async function getPerson({ queryKey }: { queryKey: [string, { id: number }] }) {
  const [, { id }] = queryKey;

  const response = await fetch(`http://localhost:3004/people/${id}`);
  if (!response.ok) {
    throw Error("Problem fetching person");
  }
  const data = await response.json();
  assertIsPerson(data);
  return data;
}

fetch is used to make the request, and an error is thrown if the response isn’t successful.

The fetching function is expected to return the response data that we want to use in our component. We use a type assert function called assertIsPerson to ensure the data is correctly typed:

function assertIsPerson(data: any): asserts data is Person {
  if (!("id" in data && "firstName" in data && "lastName" in data)) {
    throw new Error("Not a person");
  }
}

We can use the state variables from the query as follows:

export function App() {
  ...
  return (
    <div>
      {data && (        <form>
          <p>ID: {data.id}</p>          <fieldset>
            <div>
              <label htmlFor="firstName">First name</label>
              <input
                type="text"
                id="firstName"
                name="firstName"
                defaultValue={data.firstName}              />
            </div>
            <div>
              <label htmlFor="lastName">Last name</label>
              <input
                type="text"
                id="lastName"
                name="lastName"
                defaultValue={data.lastName}              />
            </div>
          </fieldset>
          <button type="submit">Save</button>
        </form>
      )}    </div>
  );
}

We show the person’s id and default the input values with the person’s first and last name.

Let’s start to implement the form submission:

export function App() {
  ...
  function handleSubmit(e: React.FormEvent<HTMLFormElement>) {    e.preventDefault();    const formData = new FormData(e.target as HTMLFormElement);    const firstName = formData.get("firstName") as string;    const lastName = formData.get("lastName") as string;  }  return (
    <div>
      {data && (
        <form onSubmit={handleSubmit}>          ...
        </form>
      )}
    </div>
  );
}

The submit handler extracts the first and last name values from the form. We will finish this implementation later.

Declaring a mutation

React Query can manage a request that updates data using a useMutation hook.

Let’s declare our mutation below the query:

export function App() {
  ...
  const { mutate, status: mutateStatus, error: mutateError } = useMutation<
    Person, // return type
    Error,
    Person  // params type
  >(updatePerson);
  ...
}

We have aliased the destructured state variables so that they don’t clash with those from the query.

The destructured mutate variable is what we will eventually use to trigger the update.

useMutation takes in a mutation function that will make the request, which is updatePerson in our example. We will look at this a little later.

useMutation accepts generic type parameters. There are various overloads, but we have passed in the following:

  1. The return type from the mutation function.
  2. The error type from the mutation function.
  3. The parameter type of the mutation function.

Using mutation state variable

We can use the mutation state variables in the form as follows:

<form onSubmit={handleSubmit}>
  <p>ID: {data.id}</p>
  <fieldset disabled={mutateStatus === "loading"}>    ...
  </fieldset>
  <button type="submit">Save</button>
  {mutateStatus === "success" && <p>Change successfully saved</p>}  {mutateStatus === "error" && <p>{mutateError!.message}</p>}</form>

We disable the update while it is in progress. We also display success/error messages when the update has been completed.

Triggering a mutation

We can trigger the mutation as follows in the submit handler:

function handleSubmit(e: React.FormEvent<HTMLFormElement>) {
  e.preventDefault();
  const formData = new FormData(e.target as HTMLFormElement);
  const firstName = formData.get("firstName") as string;
  const lastName = formData.get("lastName") as string;
  mutate({ id: data!.id, firstName, lastName });}

If we give the form a try, the update is successfully made:

Successful update

Nice. 😊

The code in this post is available in the following gist: https://gist.github.com/carlrip/c048b9cad627fde1836a55ac24a31474

Did you find this post useful?

Let me know by sharing it on Twitter.
Click here to share this post on Twitter

If you to learn more about using TypeScript with React, you may find my course useful:

Using TypeScript with React

Find out more