Why should you use react query for your react app - part 2

Why should you use react query for your react app - part 2

ยท

8 min read

...For the introduction to react-query and useQuery hooks, Read my previous post Part one

In my previous post, we explored the introduction to react query and useQuery hook.

In this post, we explore useMutation hook in data creation, edition and deletion.

We will use Free API for creating users, editing user info, and viewing and deleting users. In order to use the free API above, you will need to log in to get your access token for authorization.

Let's create a form that to create user info. The required user info are name, gender, email and status.

const App = () => {
  const handleSubmit = (e) => {
    e.preventDefault();
    // submit user data
    const dataToSubmit = {
      name: e.target[0].value,
      email: e.target[1].value,
      gender: e.target[2].value,
      status: e.target[3].value,
    };
  };

  return (
    <div className="App">
      <form onSubmit={handleSubmit}>
        <label>
          Name:
          <input type="text" name="name" required placeholder="Name" />
        </label>
        <br />
        <label>
          Email:
          <input type="email" name="email" required placeholder="Email" />
        </label>
        <br />
        <label>
          Gender:
          <select
            name="gender"
            defaultValue=""
            required
            placeholder="Select gender"
          >
            <option disabled value="">
              -- select an option --
            </option>
            <option value="male">Male</option>
            <option value="female">Female</option>
          </select>
        </label>
        <br />
        <label>
          Status:
          <select
            name="status"
            defaultValue=""
            required
            placeholder="Select status"
          >
            <option disabled value="">
              -- select an option --
            </option>
            <option value="active">Active</option>
            <option value="inactive">Inactive</option>
          </select>
        </label>
        <br />
        <button>Create user</button>
      </form>
    </div>
  );
};

export default App;

Now that we can collect info, let's create our first user, import useMutation from react-query

useMutation provides data and methods/callbacks to run and get data.

import { useMutation } from "react-query"

const App = () => {
 const {
   // responses and functions to call mutation
   data,
   error,
   isError,
   isIdle,
   isLoading,
   isPaused,
   isSuccess,
   mutate,
   mutateAsync,
   reset,
   status,
 } = useMutation(
   // the function to call an api (accepts variables)
   mutationFn, 
   // options and methods/callbacks
   {
     mutationKey,
     onError,
     onMutate,
     onSettled,
     onSuccess,
     retry,
     retryDelay,
     useErrorBoundary,
     meta,
   }
)

// this will run synchronous
 mutate(variables, {
   onError,
   onSettled,
   onSuccess,
 })

// this will return a promise
mutateAsync(variables, {
   onError,
   onSettled,
   onSuccess,
 })
}

Learn More

From the sample above, let's create our first user.

import axios from "axios";
import { useMutation } from "react-query";

const App = () => {
  const { data, error, isLoading, mutate } = useMutation(
    async (dataToSubmit) => {
      // call axios request
      const { data } = await axios.post(
        "https://gorest.co.in/public/v2/users?access-token=89945d7d4cea090a8135a824fa3100f5a057ee2c4bdf9c3b8ab01f7a20b16784",
        dataToSubmit,
        {
          headers: {
            "Content-Type": "application/json",
          },
        }
      );
      return data;
    }
  );

  const handleSubmit = (e) => {
    e.preventDefault();
    // submit user data
    const dataToSubmit = {
      name: e.target[0].value,
      email: e.target[1].value,
      gender: e.target[2].value,
      status: e.target[3].value,
    };
    mutate(dataToSubmit);
  };

  console.log(data, error);

  return (
    <div className="App">
      <form onSubmit={handleSubmit}>
        <label>
          Name:
          <input
            type="text"
            name="name"
            required
            disabled={isLoading}
            placeholder="Name"
          />
        </label>
        <br />
        <label>
          Email:
          <input
            type="email"
            name="email"
            required
            disabled={isLoading}
            placeholder="Email"
          />
        </label>
        <br />
        <label>
          Gender:
          <select
            name="gender"
            defaultValue=""
            required
            disabled={isLoading}
            placeholder="Select gender"
          >
            <option disabled value="">
              -- select an option --
            </option>
            <option value="male">Male</option>
            <option value="female">Female</option>
          </select>
        </label>
        <br />
        <label>
          Status:
          <select
            name="status"
            defaultValue=""
            required
            disabled={isLoading}
            placeholder="Select status"
          >
            <option disabled value="">
              -- select an option --
            </option>
            <option value="active">Active</option>
            <option value="inactive">Inactive</option>
          </select>
        </label>
        <br />
        <button disabled={isLoading}>
          {isLoading ? "Creating user" : "Create user"}
        </button>
      </form>
    </div>
  );
};

export default App;

Run the code above to create your first user. Congratulations on creating your first user ๐ŸŽ‰.

Let's create a formal way to represent the flow, from creating a new user to viewing all users to viewing a single user to editing a single user and finally deleting a single user. We will have mainly three screens, First the screen for viewing all users - will include a button to create a new user and a list of all users. Second is a screen we just created, for creating a single user ( we can make it available for editing users as well. ) Third is a screen for viewing a single user with buttons to edit and delete users.

First, install react-router-dom for easy routing. run

npm install react-router-dom

Then wrap your application in the Browser router. in main.jsx or index.jsx with vite it will look like this

import React from "react";
import ReactDOM from "react-dom/client";
import { QueryClient, QueryClientProvider } from "react-query";
import { BrowserRouter as Router } from "react-router-dom";
import App from "./App";

const queryClient = new QueryClient();

ReactDOM.createRoot(document.getElementById("root")).render(
  <React.StrictMode>
    <QueryClientProvider client={queryClient}>
      <Router>
        <App />
      </Router>
    </QueryClientProvider>
  </React.StrictMode>
);

Lets create our pages

users.jsx page

import axios from "axios";
import React from "react";
import { useQuery } from "react-query";
import { Link } from "react-router-dom";

const Users = () => {
  const { data, isLoading, error, refetch } = useQuery({
    queryKey: ["FETCH_USERS"],
    queryFn: async () => {
      const { data } = await axios.get("https://gorest.co.in/public/v2/users", {
        headers: {
          Authorization:
            "Bearer 89945d7d4cea090a8135a824fa3100f5a057ee2c4bdf9c3b8ab01f7a20b16784",
        },
      });
      return data;
    },
  });

  if (isLoading) {
    return <p>Please Wait...</p>;
  }
  if (error) {
    return (
      <div>
        <p>{error?.message}</p>
        <button onClick={() => refetch()}>Retry</button>
      </div>
    );
  }

  return (
    <div>
      <div
        style={{
          marginBottom: "1rem",
        }}
      >
        <Link to={"/mutate-user"}>Create New User</Link>
      </div>
      <ul>
        {data?.map((user) => (
          <li key={user?.id}>
            <Link to={`/user/${user?.id}`}>{user?.name}</Link>
          </li>
        ))}
      </ul>
    </div>
  );
};

export default Users;

user.jsx

import axios from "axios";
import React from "react";
import { useMutation, useQuery } from "react-query";
import { Link, useNavigate, useParams } from "react-router-dom";

const User = () => {
  // navigation hook
  const navigate = useNavigate();

  // get url params
  const params = useParams();
  const id = params?.id;

  const { data, isLoading, error, refetch } = useQuery({
    queryKey: ["FETCH_USER", id],
    queryFn: async () => {
      const { data } = await axios.get(
        `https://gorest.co.in/public/v2/users/${id}`,
        {
          headers: {
            Authorization:
              "Bearer 89945d7d4cea090a8135a824fa3100f5a057ee2c4bdf9c3b8ab01f7a20b16784",
          },
        }
      );
      return data;
    },
  });

  //   delete mutation
  const {
    error: mutateError,
    isLoading: mutateLoading,
    mutateAsync,
  } = useMutation(async () => {
    // call axios request
    const { data } = await axios.delete(
      `https://gorest.co.in/public/v2/users/${id}?access-token=89945d7d4cea090a8135a824fa3100f5a057ee2c4bdf9c3b8ab01f7a20b16784`,
      {
        headers: {
          "Content-Type": "application/json",
        },
      }
    );
    return data;
  });

  if (isLoading) {
    return <p>Please Wait...</p>;
  }
  if (error) {
    return (
      <div>
        <p>{error?.message}</p>
        <button onClick={() => refetch()}>Retry</button>
      </div>
    );
  }

  return (
    <div>
      <div
        style={{
          display: "flex",
          gap: "1rem",
          marginBottom: "1rem",
        }}
      >
        <Link to={`/mutate-user?id=${id}`}>Edit User</Link>
        <button
          onClick={async () => {
            await mutateAsync();
            // if success delete redirect to all users page
            navigate("/");
          }}
        >
          {mutateLoading ? "Deleting user" : "Delete User"}
        </button>
      </div>
      {mutateError && (
        <p
          style={{
            color: "red",
          }}
        >
          {mutateError?.message}
        </p>
      )}
      <div>
        <p>Name: {data?.name}</p>
        <p>Email: {data?.email}</p>
        <p>Gender: {data?.gender}</p>
        <p>Status: {data?.status}</p>
      </div>
    </div>
  );
};

export default User;

mutate-user.jsx

import axios from "axios";
import { useEffect } from "react";
import { useMutation, useQuery } from "react-query";
import { useNavigate, useSearchParams } from "react-router-dom";

const MutateUser = () => {
  // navigate hook
  const navigate = useNavigate();
  // find ID if present
  const [searchParams] = useSearchParams();
  const id = searchParams.get("id");

  //   get user if there is an ID
  const {
    data: userData,
    isLoading: userLoading,
    error: userError,
    refetch,
  } = useQuery({
    queryKey: ["FETCH_USER", id],
    queryFn: async () => {
      const { data } = await axios.get(
        `https://gorest.co.in/public/v2/users/${id}?access-token=89945d7d4cea090a8135a824fa3100f5a057ee2c4bdf9c3b8ab01f7a20b16784`,
        {
          headers: {
            Authorization:
              "Bearer 89945d7d4cea090a8135a824fa3100f5a057ee2c4bdf9c3b8ab01f7a20b16784",
          },
        }
      );
      return data;
    },
    enabled: !!id,
  });

  const { data, error, isLoading, mutateAsync } = useMutation(
    async (dataToSubmit) => {
      // call axios request
      // call post if its user creation and put if its user editing
      const { data } = await axios[id ? "put" : "post"](
        `https://gorest.co.in/public/v2/users${
          // pass id if editing user
          id ? "/" + id : ""
        }?access-token=89945d7d4cea090a8135a824fa3100f5a057ee2c4bdf9c3b8ab01f7a20b16784`,
        dataToSubmit,
        {
          headers: {
            "Content-Type": "application/json",
          },
        }
      );
      return data;
    }
  );

  // navigate to user page when request was successfully
  useEffect(() => {
    if (data) {
      // redirect to user page
      navigate(`/user/${data?.id}`);
    }
  }, [data]);

  if (userLoading) {
    return <p>Please Wait...</p>;
  }

  if (userError) {
    return (
      <div>
        <p>{userError?.message}</p>
        <button onClick={() => refetch()}>Retry</button>
      </div>
    );
  }

  const handleSubmit = async (e) => {
    e.preventDefault();
    // submit user data
    const dataToSubmit = {
      name: e.target[0].value,
      email: e.target[1].value,
      gender: e.target[2].value,
      status: e.target[3].value,
    };
    await mutateAsync(dataToSubmit);
  };

  return (
    <div className="App">
      <form onSubmit={handleSubmit}>
        <label>
          Name:
          <input
            type="text"
            name="name"
            // prefill data if exists
            defaultValue={userData?.name ?? ""}
            required
            disabled={isLoading}
            placeholder="Name"
          />
        </label>
        <br />
        <label>
          Email:
          <input
            type="email"
            name="email"
            // prefill data if exists
            defaultValue={userData?.email ?? ""}
            required
            disabled={isLoading}
            placeholder="Email"
          />
        </label>
        <br />
        <label>
          Gender:
          <select
            name="gender"
            // prefill data if exists
            defaultValue={userData?.gender ?? ""}
            required
            disabled={isLoading}
            placeholder="Select gender"
          >
            <option disabled value="">
              -- select an option --
            </option>
            <option value="male">Male</option>
            <option value="female">Female</option>
          </select>
        </label>
        <br />
        <label>
          Status:
          <select
            name="status"
            // prefill data if exists
            defaultValue={userData?.status ?? ""}
            required
            disabled={isLoading}
            placeholder="Select status"
          >
            <option disabled value="">
              -- select an option --
            </option>
            <option value="active">Active</option>
            <option value="inactive">Inactive</option>
          </select>
        </label>
        <br />
        {error && (
          <p
            style={{
              color: "red",
            }}
          >
            {error?.message}
          </p>
        )}
        <br />
        <button disabled={isLoading}>
          {!id && <>{isLoading ? "Creating user..." : "Create user"}</>}
          {id && <>{isLoading ? "Editing user..." : "Edit user"}</>}
        </button>
      </form>
    </div>
  );
};

export default MutateUser;

Then let's set up our routing system

in the App.jsx

import React from "react";
import { Route, Routes } from "react-router-dom";
import MutateUser from "./pages/mutate-user";
import User from "./pages/user";
import Users from "./pages/users";

const App = () => {
  return (
    <div>
      <Routes>
        <Route element={<Users />} path="/" />
        <Route element={<User />} path="/user/:id" />
        <Route element={<MutateUser />} path="/mutate-user" />
      </Routes>
    </div>
  );
};

export default App;

Congratulations ๐ŸŽ‰, we have created a CRUD application with react-query Without managing any state ourselves Phew.

If you missed the introduction part and useQuery part, read my previous post

If you like this article there are more like this in our blogs, follow us on dev.to/clickpesa, medium.com/clickpesa-engineering-blog and clickpesa.hashnode.dev

Happy Hacking!!

ย