Easy State Management with React Query and Typescript for your SaaS app

SaaS apps constantly contact the backend APIs to create, read, update, and delete data - CRUD. Using libraries like Redux to do state management with their complex boilerplate of Actions, Reducers, and Middlewares will get complicated as the size of the application grows. Redux Query keeps it simple.

I'm using React Query in my SaaS app, launchman.io to fetch a list of sites data. When a new site is created, a POST request is made. The list of sites is then re-fetched, and a toast is displayed when the request is successful.

0:00
/
Using React Query in SaaS app

In this post, you will learn how to:

  1. Fetch data using React Query and GET request
  2. Create new data using React Query and POST request
  3. Update data using React Query and PATCH request
  4. Show Toast when an API request is successful

Install React Query

npm i @tanstack/react-query

Fetch data using React Query

Let's start nice and easy with a simple GET request.

We can get the list of sites owned by a user with a specified email by sending out a GET request using Axios. React Query has a hook called useQuery that takes in:

  1. An array of keys - Used to name the query's data. It should be unique and serializable. If a variable is present, it is also passed onto the query function.
  2. A function to call - The Axios request that will run when the query is called.
import axios from "axios";
import { useQuery, useQueryClient } from "@tanstack/react-query";

const fetchSites = async (email: string) => {
  const response = await axios.get(`api/users/${email}`);
  return response;
};

const useSites = (email: string) => {
  return useQuery(["sites", email], () => fetchSites(email));
};

export {
  useSites
};

We can now consume these hooks in a React component by importing useSites and use it like so:

import { FunctionComponent } from "react";
import { useSites } from "../../hooks/api";

interface AppProps {
  
}
 
const App: FunctionComponent<AppProps> = () => {
  const { data, isLoading, isFetching } = useSites("sukh@email.com");

  if (isLoading){
    return <p>Loading...</p>
  }

  return <div>{JSON.stringify(data)}</div>;
}
 
export default App;

Notice that you get the status of the query for free with React Query. You can use this to show Loading states in the UI.

Now that we can fetch our data. Let's take a look at how we can create new data.

Create new data using React Query

When creating a new resource, you should be making a POST request with Axios with the body containing the data.

React Query has another hook specifically for mutations called useMutation. It takes just the function to call when the hook is used. This would be your Axios request.

When the new data is created, your previously fetched data will be stale. This is where useMutation lets you specify a onSuccess function that runs right after the query is successful. Using queryClient the key you specified in useSites hook, the data will automatically be re-fetched.

import axios from "axios";
import { useMutation, useQueryClient } from "@tanstack/react-query";

const useCreateSite = () => {
  const queryClient = useQueryClient();

  return useMutation((data: any) => axios.post("/api/sites", data), {
    onSuccess: () => {
      queryClient.invalidateQueries(["sites"]);

      // good place to throw a toast/notification to let the user know
    },
  });
};

export {
  useCreateSite
};

In your React component, you can trigger a mutation like so:

import { FunctionComponent } from "react";
import { useSites } from "../../hooks/api";

interface AppProps {
  
}
 
const App: FunctionComponent<AppProps> = () => {
  const { mutate: createSiteMutation } = useCreateSite();


  return <div><button onClick={()=>{
      createSiteMutation({
        data
      });
  }}></button></div>;
}
 
export default App;

Update data using React Query

Updating data is largely the same as Creating in the last section. You can useMutation to specify a PATCH Axios request and pass in the data to update. Once successful, invalidate the query using the query key you defined in the useSites.

const useUpdateSite = () => {
  const queryClient = useQueryClient();

  return useMutation(
    ({ subdomain, ...data }: any) =>
      axios.patch(`/api/sites/${subdomain}`, data),
    {
      onSuccess: () => {
        queryClient.invalidateQueries(["sites"]);
      },
    }
  );
};

export {
  useUpdateSite
};

To use in React component:

import { FunctionComponent } from "react";
import { useUpdateSite } from "../../hooks/api";

interface AppProps {
  
}
 
const App: FunctionComponent<AppProps> = () => {
  const { mutate: updateSiteMutation } = useUpdateSite();


  return <div><button onClick={()=>{useUpdateSite({
        data, subdomain
      });}}></button></div>;
}
 
export default App;

Bonus: Show a Toast when the API request is successful

When a user creates new data, it's good UX practice to show a notification on success. We can do this very quickly with React Query. I'm using the Toast component from Chakra UI but you can use another library like React Toastify as well.

import axios from "axios";
import { useMutation, useQueryClient } from "@tanstack/react-query";
import { useToast } from "@chakra-ui/toast";

const useCreateSite = () => {
  const queryClient = useQueryClient();
  const toast = useToast();

  return useMutation((data: any) => axios.post("/api/sites", data), {
    onSuccess: () => {
      queryClient.invalidateQueries(["sites"]);
		
      toast({
          title: "Created site.",
          description: "We've created a new site for you.",
          status: "success",
          duration: 9000,
          isClosable: true,
        });
    },
  });
};

export {
  useCreateSite
};