...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,
})
}
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!!