...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 user, editing user info, viewing and deleting user. In order to use the free api above, you will need to login to get your access token for authorization.
Lets 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, lets 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, lets 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 🎉.
Lets create a formal way to represent the flow, from creating a new user to viewing all users to viewing single user to editing single user and finally deleting single user. We will have mainly three screens,
First the screen for viewing all users - it will include a button to create 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 user as well. )
Third is a screen for viewing single user with buttons to edit and delete user.
First install react-router-dom
for easy routing. run
npm install react-router-dom
Then wrap your application in 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 lets setup 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 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!!
Top comments (0)