Setup React Query and using it

what is React Query?

it is a data-fetching and state management library for React applications that simplifies fetching, caching, and updating data.

how to install it ?

npm i @tanstack/react-query

First we need to import QueryClient class and QueryClientProvider function in main.jsx/js file

Notice: download ReactQueryDevtools for development :

npm i @tanstack/react-query-devtools

Then run npm run dev command , you should now see this icon at the bottom

when you click on it , this panel will be shown

from @tanstack/react-query :

import { QueryClient, QueryClientProvider } from "@tanstack/react-query";

we must to wrapp my app component with QueryClientProvider component , then creating a QueryClient instance and passing it with a "client" prop name

import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { ReactQueryDevtools } from "@tanstack/react-query-devtools";
const queryClient = new QueryClient();

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

React Query library will care about fetching data asynchronously

// Utils.js
/*& In this snippet, we are importing the Axios library and 
creating a custom Axios instance called customFetch. This instance 
is configured with a base URL of "http://localhost:5000/api/tasks."

*/
import axios from "axios";

const customFetch = axios.create({
  baseURL: "http://localhost:5000/api/tasks",
});

export default customFetch;

Now here we will destructure the returned object data from axios library .

The useQuery hook takes an object as its argument, where you specify the queryKey (a unique identifier for the query) and the queryFn (a function that returns the data).

In this case, the queryFn is using the customFetch instance to make a GET request to the "/api/tasks" endpoint. The result is stored in the data variable, and the component renders the fetched data using the SingleItem component for each item in the "taskList."

import { useQuery } from "@tanstack/react-query";
import SingleItem from "./SingleItem";
import customFetch from "./utils";

const Items = () => {
  const { isLoading, data, isError, error } = useQuery({
  queryKey: ["tasks"], //behind the scene reactquery will cache the data
  queryFn: async () => {
    const response = await customFetch.get("/");
    return response.data;
  },
});


  if (isLoading) {
    return <p style={{ marginTop: "1rem" }}>Loading...</p>;
  }

  if (isError) {
    return <p style={{ marginTop: "1rem" }}>There was an error...</p>;
  }

  // if (error) {
  //   return <p style={{ marginTop: "1rem" }}>{error.response.data}</p>;
  // }

  return (
    <div className="items">
      {data.data.taskList.map((item) => {
        return <SingleItem key={item.id} item={item} />;
      })}
    </div>
  );
};
export default Items;

Now i want to explain something before , useQuery Hook uses for getting and fetching data on the server , maybe you can it for sending data like the example below but u should know that isn't designed for this.

const { isLoading, data, isError, error } = useQuery({
  queryKey: ["createTask"],
  queryFn: () => customFetch.post("/", { title: newItemName }),
});

Instead we must use useMutationhooks for adding or creating....a resource. for example:

import { useMutation } from "@tanstack/react-query";
import { useState } from "react";
import customFetch from "./utils";

const Form = () => {
  const [newItemName, setNewItemName] = useState("");

  const { mutate: createTask, isLoading } = useMutation({
    mutationFn: (taskTitle) => customFetch.post("/", { title: taskTitle }),
  });

  const handleSubmit = (e) => {
    e.preventDefault();
    createTask(newItemName);
  };
  return (
    <form action="http://localhost:5000/api/tasks" onSubmit={handleSubmit}>
      <h4>task bud</h4>
      <div className="form-control">
        <input
          type="text "
          className="form-input"
          value={newItemName}
          onChange={(event) => setNewItemName(event.target.value)}
        />
        <button type="submit" className="btn" disabled={isLoading}>
          add task
        </button>
      </div>
    </form>
  );
};
export default Form;

Now what is the difference between useQuery and useMutation hooks ?

useQuery HooksuseMutation hooks
useMutation doesn't actually execute the mutation automatically! Instead, the useMutation hook returns an array with two elements. The first element is the mutate function we'll use to actually run the mutation later onunlike with useQueryexecute the mutation automatically!
for maintaining and retrieving data through APIsfor creating , updating and deleting data through APIs

Now maybe we create the new Task right?? but did u notice that is not appeared directly on the page without refreshing ? Now here "useQueryClient" hook role cames,

This hook is used to access the query client instance. This is necessary for manually invalidating queries (queryClient.invalidateQueries) when new data is created to ensure that the UI stays up-to-date.

now we want to trigger a refetch of the updated data. when the user successfully created a new resource (or Task in our example) , so after adding it There is onSuccess and onError Callback provides by useMutation hook .

in onSuccess callback we can trigger a refetch of the updated data + notify the user that the task has created successfully using react-toastify library + Clear the input field after a successful task creation

import { useMutation, useQueryClient } from "@tanstack/react-query";
import { useState } from "react";
import customFetch from "./utils";
import { toast } from "react-toastify";

const Form = () => {
  const [newItemName, setNewItemName] = useState("");
  const queryClient = useQueryClient();
  const { mutate: createTask, isLoading } = useMutation({
    mutationFn: (taskTitle) => customFetch.post("/", { title: taskTitle }),
    onSuccess: () => {
      queryClient.invalidateQueries({ queryKey: ["tasks"] });
      toast.success("task added");
      setNewItemName("");
    },
    onError: (error) => {
      toast.error(error.response.data.msg);
    },
  });

  const handleSubmit = (e) => {
    e.preventDefault();
    createTask(newItemName);
  };
  return (
    <form action="http://localhost:5000/api/tasks" onSubmit={handleSubmit}>
      <h4>task bud</h4>
      <div className="form-control">
        <input
          type="text "
          className="form-input"
          value={newItemName}
          onChange={(event) => setNewItemName(event.target.value)}
        />
        <button type="submit" className="btn" disabled={isLoading}>
          add task
        </button>
      </div>
    </form>
  );
};
export default Form;