React Query and Axios (Typescript) example with Rest API

React Query is written in JavaScript. In this tutorial, I will show you how to build a React Query and Axios example (in Typescript) working with Rest API, display and modify data (CRUD operations) with Hooks.

More Practice:
React Hook Form Typescript example with Validation
React Typescript and Axios (without React Query) with API call example
React Table example: CRUD App | react-table 7
React Hooks File Upload example with Axios & Progress Bar
React Typescript Authentication example with Hooks

Serverless with Firebase:
React Hooks + Firebase Realtime Database: CRUD App
React Hooks + Firestore example: CRUD app


React Query overview

Most state management libraries (including Redux) are good for working with client state, but not for server state. It’s because server state is persisted remotely in a location the client side cannot control, it can become outdate in our applications and we need to make asynchronous APIs for fetching and updating.

React Query is one of the best libraries for managing server state. It helps us fetch, cache, synchronize and update data without touching any global state.

React Query helps us:

  • remove complicated and misunderstood code and replace with several React Query logic
  • easier to maintain and build new features without worrying about wiring up new server state data sources
  • make our application feel faster and more responsive
  • save bandwidth and increase memory performance

React Query and Axios with Typescript example

We will build a React Typescript Client with React Query and Axios library to make CRUD requests to Rest API in that:

  • React Query Axios Typescript GET request: get all Tutorials, get Tutorial by Id, find Tutorial by title
  • React Query Axios Typescript POST request: create new Tutorial
  • React Query Axios Typescript PUT request: update an existing Tutorial
  • React Query Axios Typescript DELETE request: delete a Tutorial, delete all Tutorials

react-query-axios-typescript-example

This React Query Axios Typescript Client works with the following Web API:

MethodsUrlsActions
POST/api/tutorialscreate new Tutorial
GET/api/tutorialsretrieve all Tutorials
GET/api/tutorials/:idretrieve a Tutorial by :id
PUT/api/tutorials/:idupdate a Tutorial by :id
DELETE/api/tutorials/:iddelete a Tutorial by :id
DELETE/api/tutorialsdelete all Tutorials
GET/api/tutorials?title=[keyword]find all Tutorials which title contains keyword

You can find step by step to build a Server like this in one of these posts:

Remember that you need to configure CORS: Access-Control-Allow-Origin: *.
It helps the REST APIs can be accessed by any origin.

Setup React Query Axios Typescript Project

Open cmd at the folder you want to save Project folder, run command:
npx create-react-app react-typescript-authentication-example --template typescript

Import Bootstrap

There are two ways:

– Installing bootstrap module:
yarn add [email protected]
Or: npm install [email protected].

Open src/App.tsx and modify the code inside it as following-

...
import "bootstrap/dist/css/bootstrap.min.css";

const App: React.FC = () => {
  return (
    // ...
  );
}

export default App;

– Using CDN: Open public/index.html and add <link> element.

<!DOCTYPE html>
<html lang="en">
  <head>
    ...
    <link
      rel="stylesheet"
      href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css"
    />
    <title>React Query Axios Typescript example</title>
  </head>
  <body>
    ...
  </body>
</html>

Setup React Query and Axios

React Query

Install React Query module:

  • Using npm:
  • $ npm install react-query
  • Using yarn:
  • $ yarn add react-query

Let’s create new QueryClient to interact with a cache.

Open index.tsx, wrap App component with QueryClientProvider component which connects and provides QueryClient object to our application.

src/index.tsx

import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
...

import { QueryClient, QueryClientProvider } from "react-query";
const queryClient = new QueryClient();

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

From official Document Important Defaults section, React Query will consider cached data as stale. Stale queries are re-fetched automatically in the background when:

  • New instances of the query mount
  • The window is refocused
  • The network is reconnected
  • The query is optionally configured with a refetch interval.

You can turn off most of the defaults by passing defaultOptions as config parameter. For example:

const queryClient = new QueryClient({
  defaultOptions: {
    queries: {
      refetchOnWindowFocus: false,
      refetchOnmount: false,
      refetchOnReconnect: false,
      retry: false,
      staleTime: 5*60*1000,
    },
  },
});
  • refetchOnWindowFocus: automatically requests fresh data in the background if user leaves the app and returns to stale data.
  • refetchOnmount: if true, refetch on mount if the data is stale.
  • refetchOnReconnect: if true, refetch on reconnect if the data is stale.
  • retry: if true, failed queries will retry infinitely.
  • staleTime: the time in milliseconds after data is considered stale. Defaults to 0.

Axios

Install axios module:

Define Data Type

Now we need to define the data type for Tutorial. Create and export Tutorial interface in types/Tutorial.ts.

export default interface Tutorial {
  id?: any | null,
  title: string,
  description: string,
  published?: boolean,
}

Create Service using Axios

In this step, we’re gonna create a service that uses Axios object above to send HTTP requests.

First we create a new instance of axios using axios.create(config) method as apiClient.

import axios from "axios";

const apiClient = axios.create({
  baseURL: "http://localhost:8080/api",
  headers: {
    "Content-type": "application/json",
  },
});
...

Now we can use apiClient to send HTTP requests and receive responses.

The response for a Axios request contains:

  • data: parsed response body provided by the server
  • status: HTTP status code
  • statusText: HTTP status message
  • headers: HTTP headers (lower case)
  • config: the request config that was provided to axios
  • request: the last client request instance that generated this response
For more details about Axios (with instance creation, params, json, body, headers, error handling…), kindly visit:
Axios Tutorial: Get/Post/Put/Delete request example

Then the service exports CRUD functions and finder method:

  • CREATE: create
  • RETRIEVE: findAll, findById
  • UPDATE: update
  • DELETE: deleteById, deleteAll
  • FINDER: findByTitle

services/TutorialService.ts

import axios from "axios";
import Tutorial from "../types/Tutorial";

const apiClient = axios.create({
  baseURL: "http://localhost:8080/api",
  headers: {
    "Content-type": "application/json",
  },
});

const findAll = async () => {
  const response = await apiClient.get<Tutorial[]>("/tutorials");
  return response.data;
}

const findById = async (id: any) => {
  const response = await apiClient.get<Tutorial>(`/tutorials/${id}`);
  return response.data;
}

const findByTitle = async (title: string) => {
  const response = await apiClient.get<Tutorial[]>(`/tutorials?title=${title}`);
  return response.data;
}

const create = async ({ title, description }: Tutorial) => {
  const response = await apiClient.post<any>("/tutorials", { title, description });
  return response.data;
}

const update = async (id: any, { title, description, published }: Tutorial) => {
  const response = await apiClient.put<any>(`/tutorials/${id}`, { title, description, published });
  return response.data;
}

const deleteById = async (id: any) => {
  const response = await apiClient.delete<any>(`/tutorials/${id}`);
  return response.data;
}

const deleteAll = async () => {
  const response = await apiClient.delete<any>("/tutorials");
  return response.data;
}

const TutorialService = {
  findAll,
  findById,
  findByTitle,
  create,
  update,
  deleteById,
  deleteAll
}

export default TutorialService;

React Query Axios Typescript GET

To fetch JSON data from API, we use React Query useQuery hook:

Now look at following simple example:

import { useQuery } from 'react-query'

function App() {
  const { isLoading, isSuccess, isError, data, error, refetch } =
                 useQuery<Tutorial[], Error>('query-tutorials', fetchTutorials, { enabled: false, retry: 2, onSuccess, onError });
}

'query-tutorials' (queryKey) (Required): The query will automatically update when this key changes (in case enabled is not set to false). If you want to run the query everytime title changes, you can use queryKey like this: ['query-tutorials', title].

fetchTutorials (queryFn) is async function that returns a Promise.

enabled: if false, disable this query from automatically running.
retry: if true, failed queries will retry infinitely. If set to a number, failed queries will retry that number of times. By default, queries are silently retried 3 times, with exponential backoff delay before capturing and displaying an error to the UI.

onSuccess: the callback function that will fire any time the query successfully fetches new data.
onError: the callback function that will fire if the query encounters an error and will be passed the error.

useQuery result contains:

  • isLoading: true if there is no cached data and the query is currently fetching.
  • isSuccess: true if the query has received a response with no errors and ready to display data, data property is the data received from the successful fetch.
  • isError: true if the query has failed with an error. error property has the error received from the attempted fetch.
  • refetch: the function to manually refetch the query.

There are many properties that you can find at useQuery Reference.

Let’s implement a React Typescript component with React Query and Axios that can:

  • get all Tutorials
  • get Tutorial by Id
  • find Tutorial by title
import React, { useState, useEffect } from "react";
import { useQuery, useMutation } from "react-query";

import Tutorial from "./types/Tutorial"
import TutorialService from "./services/TutorialService"

const App: React.FC = () => {
  const [getId, setGetId] = useState("");
  const [getTitle, setGetTitle] = useState("");

  const [getResult, setGetResult] = useState<string | null>(null);

  const fortmatResponse = (res: any) => {
    return JSON.stringify(res, null, 2);
  };

  const { isLoading: isLoadingTutorials, refetch: getAllTutorials } = useQuery<Tutorial[], Error>(
    "query-tutorials",
    async () => {
      return await TutorialService.findAll();
    },
    {
      enabled: false,
      onSuccess: (res) => {
        setGetResult(fortmatResponse(res));
      },
      onError: (err: any) => {
        setGetResult(fortmatResponse(err.response?.data || err));
      },
    }
  );

  useEffect(() => {
    if (isLoadingTutorials) setGetResult("loading...");
  }, [isLoadingTutorials]);

  function getAllData() {
    try {
      getAllTutorials();
    } catch (err) {
      setGetResult(fortmatResponse(err));
    }
  }

  const { isLoading: isLoadingTutorial, refetch: getTutorialById } = useQuery<Tutorial, Error>(
    "query-tutorial-by-id",
    async () => {
      return await TutorialService.findById(getId);
    },
    {
      enabled: false,
      retry: 1,
      onSuccess: (res) => {
        setGetResult(fortmatResponse(res));
      },
      onError: (err: any) => {
        setGetResult(fortmatResponse(err.response?.data || err));
      },
    }
  );

  useEffect(() => {
    if (isLoadingTutorial) setGetResult("loading...");
  }, [isLoadingTutorial]);

  function getDataById() {
    if (getId) {
      try {
        getTutorialById();
      } catch (err) {
        setGetResult(fortmatResponse(err));
      }
    }
  }

  const { isLoading: isSearchingTutorial, refetch: findTutorialsByTitle } = useQuery<Tutorial[], Error>(
    "query-tutorials-by-title", // ["query-tutorials-by-title", getTitle],
    async () => {
      return await TutorialService.findByTitle(getTitle);
    },
    {
      enabled: false,
      retry: 1,
      onSuccess: (res) => {
        setGetResult(fortmatResponse(res));
      },
      onError: (err: any) => {
        setGetResult(fortmatResponse(err.response?.data || err));
      },
    }
  );

  useEffect(() => {
    if (isSearchingTutorial) setGetResult("searching...");
  }, [isSearchingTutorial]);

  function getDataByTitle() {
    if (getTitle) {
      try {
        findTutorialsByTitle();
      } catch (err) {
        setGetResult(fortmatResponse(err));
      }
    }
  }

  const clearGetOutput = () => {
    setGetResult(null);
  };

  return (
    <div id="app" className="container">
      <div className="card">
        <div className="card-header">React Query Axios Typescript GET - BezKoder.com</div>
        <div className="card-body">
          <div className="input-group input-group-sm">
            <button className="btn btn-sm btn-primary" onClick={getAllData}>
              Get All
            </button>

            <input
              type="text"
              value={getId}
              onChange={(e) => setGetId(e.target.value)}
              className="form-control ml-2"
              placeholder="Id"
            />
            <div className="input-group-append">
              <button className="btn btn-sm btn-primary" onClick={getDataById}>
                Get by Id
              </button>
            </div>

            <input
              type="text"
              value={getTitle}
              onChange={(e) => setGetTitle(e.target.value)}
              className="form-control ml-2"
              placeholder="Title"
            />
            <div className="input-group-append">
              <button
                className="btn btn-sm btn-primary"
                onClick={getDataByTitle}
              >
                Find By Title
              </button>
            </div>

            <button
              className="btn btn-sm btn-warning ml-2"
              onClick={clearGetOutput}
            >
              Clear
            </button>
          </div>

          {getResult && (
            <div className="alert alert-secondary mt-2" role="alert">
              <pre>{getResult}</pre>
            </div>
          )}
        </div>
      </div>
    </div>
  );
}

export default App;

The result will look like this:

react-query-axios-typescript-get-example

Find tutorial by id:

react-query-axios-typescript-search

Filter tutorials by title:

react-query-axios-typescript-filter

React Query Axios Typescript POST

To perform CREATE, UPDATE, or DELETE operations inside our React Typescript component, we use React Query useMutation hook.

import { useMutation } from 'react-query'

function App() {
  const { isLoading, isSuccess, isError, data, error, mutate } =
                 useMutation<ResultMessage, Error>(postTutorial(info), { onSuccess, onError });
}

postTutorial (mutationFn) is async function that returns a Promise.

onSuccess: the callback function that will fire any time the query successfully fetches new data.
onError: the callback function that will fire if the query encounters an error and will be passed the error.

useMutation result contains:

  • isLoading: true if there is no cached data and the query is currently fetching.
  • isSuccess: true if the query has received a response with no errors and ready to display data, data property is the data received from the successful fetch.
  • isError: true if the query has failed with an error. error property has the error received from the attempted fetch.
  • mutate: the function (with variables) to manually to trigger the mutation.

There are many properties that you can find at useMutation Reference.

Let’s use Typescript, React Query with Axios POST request to create new Tutorial.

import React, { useState, useEffect } from "react";
import { useQuery, useMutation } from "react-query";

import TutorialService from "./services/TutorialService"

const App: React.FC = () => {
  const [postTitle, setPostTitle] = useState("");
  const [postDescription, setPostDescription] = useState("");

  const [postResult, setPostResult] = useState<string | null>(null);

  const fortmatResponse = (res: any) => {
    return JSON.stringify(res, null, 2);
  };

  const { isLoading: isPostingTutorial, mutate: postTutorial } = useMutation<any, Error>(
    async () => {
      return await TutorialService.create(
        {
          title: postTitle,
          description: postDescription
        });
    },
    {
      onSuccess: (res) => {
        setPostResult(fortmatResponse(res));
      },
      onError: (err: any) => {
        setPostResult(fortmatResponse(err.response?.data || err));
      },
    }
  );

  useEffect(() => {
    if (isPostingTutorial) setPostResult("posting...");
  }, [isPostingTutorial]);

  function postData() {
    try {
      postTutorial();
    } catch (err) {
      setPostResult(fortmatResponse(err));
    }
  }

  const clearPostOutput = () => {
    setPostResult(null);
  };

  return (
    <div id="app" className="container">
      <div className="card">
        <div className="card-header">React Query Axios Typescript POST - BezKoder.com</div>
        <div className="card-body">
          <div className="form-group">
            <input
              type="text"
              value={postTitle}
              onChange={(e) => setPostTitle(e.target.value)}
              className="form-control"
              placeholder="Title"
            />
          </div>
          <div className="form-group">
            <input
              type="text"
              value={postDescription}
              onChange={(e) => setPostDescription(e.target.value)}
              className="form-control"
              placeholder="Description"
            />
          </div>
          <button className="btn btn-sm btn-primary" onClick={postData}>
            Post Data
          </button>
          <button
            className="btn btn-sm btn-warning ml-2"
            onClick={clearPostOutput}
          >
            Clear
          </button>

          {postResult && (
            <div className="alert alert-secondary mt-2" role="alert">
              <pre>{postResult}</pre>
            </div>
          )}
        </div>
      </div>
    </div>
  );
}

export default App;

Check the result by making a React Query Axios Typescript Post Request:

react-query-axios-typescript-post-example

React Query Axios Typescript PUT

We’re gonna use Typescript, React Query with Axios PUT request to update an existing Tutorial.

import React, { useState, useEffect } from "react";
import { useQuery, useMutation } from "react-query";

import TutorialService from "./services/TutorialService"

const App: React.FC = () => {
  const [putId, setPutId] = useState("");
  const [putTitle, setPutTitle] = useState("");
  const [putDescription, setPutDescription] = useState("");
  const [putPublished, setPutPublished] = useState(false);

  const [putResult, setPutResult] = useState<string | null>(null);

  const fortmatResponse = (res: any) => {
    return JSON.stringify(res, null, 2);
  };

  const { isLoading: isUpdatingTutorial, mutate: updateTutorial } = useMutation<any, Error>(
    async () => {
      return await TutorialService.update(
        putId,
        {
          title: putTitle,
          description: putDescription,
          published: putPublished
        });
    },
    {
      onSuccess: (res) => {
        setPutResult(fortmatResponse(res));
      },
      onError: (err: any) => {
        setPutResult(fortmatResponse(err.response?.data || err));
      },
    }
  );

  useEffect(() => {
    if (isUpdatingTutorial) setPutResult("updating...");
  }, [isUpdatingTutorial]);

  function putData() {
    if (putId) {
      try {
        updateTutorial();
      } catch (err) {
        setPutResult(fortmatResponse(err));
      }
    }
  }

  const clearPutOutput = () => {
    setPutResult(null);
  };

  return (
    <div id="app" className="container">
      <div className="card">
        <div className="card-header">React Query Axios Typescript PUT - BezKoder.com</div>
        <div className="card-body">
          <div className="form-group">
            <input
              type="text"
              value={putId}
              onChange={(e) => setPutId(e.target.value)}
              className="form-control"
              placeholder="Id"
            />
          </div>
          <div className="form-group">
            <input
              type="text"
              value={putTitle}
              onChange={(e) => setPutTitle(e.target.value)}
              className="form-control"
              placeholder="Title"
            />
          </div>
          <div className="form-group">
            <input
              type="text"
              value={putDescription}
              onChange={(e) => setPutDescription(e.target.value)}
              className="form-control"
              placeholder="Description"
            />
          </div>
          <div className="form-check mb-2">
            <input
              type="checkbox"
              name="putPublished"
              checked={putPublished}
              onChange={(e) => setPutPublished(e.target.checked)}
              className="form-check-input"
            />
            <label className="form-check-label" htmlFor="putPublished">
              Publish
            </label>
          </div>
          <button className="btn btn-sm btn-primary" onClick={putData}>
            Update Data
          </button>
          <button
            className="btn btn-sm btn-warning ml-2"
            onClick={clearPutOutput}
          >
            Clear
          </button>

          {putResult && (
            <div className="alert alert-secondary mt-2" role="alert">
              <pre>{putResult}</pre>
            </div>
          )}
        </div>
      </div>
    </div>
  );
}

export default App;

The result will look like this:

react-query-axios-typescript-put-example

React Query Axios Typescript DELETE

Now we implement a React Typescript component to delete data with React Query and Axios Delete method:

  • delete a Tutorial
  • delete all Tutorials
import React, { useState, useEffect } from "react";
import { useQuery, useMutation } from "react-query";

import TutorialService from "./services/TutorialService"

const App: React.FC = () => {
  const [deleteId, setDeleteId] = useState("");

  const [deleteResult, setDeleteResult] = useState<string | null>(null);

  const fortmatResponse = (res: any) => {
    return JSON.stringify(res, null, 2);
  };

  const { isLoading: isDeletingTutorials, mutate: deleteAllTutorials } = useMutation<any, Error>(
    async () => {
      return await TutorialService.deleteAll();
    },
    {
      onSuccess: (res) => {
        setDeleteResult(fortmatResponse(res));
      },
      onError: (err: any) => {
        setDeleteResult(fortmatResponse(err.response?.data || err));
      },
    }
  );

  useEffect(() => {
    if (isDeletingTutorials) setDeleteResult("deleting...");
  }, [isDeletingTutorials]);

  function deleteAllData() {
    try {
      deleteAllTutorials();
    } catch (err) {
      setDeleteResult(fortmatResponse(err));
    }
  }

  const { isLoading: isDeletingTutorial, mutate: deleteTutorial } = useMutation<any, Error>(
    async () => {
      return await TutorialService.deleteById(deleteId);
    },
    {
      onSuccess: (res) => {
        setDeleteResult(fortmatResponse(res));
      },
      onError: (err: any) => {
        setDeleteResult(fortmatResponse(err.response?.data || err));
      },
    }
  );

  useEffect(() => {
    if (isDeletingTutorial) setDeleteResult("deleting...");
  }, [isDeletingTutorial]);

  function deleteDataById() {
    if (deleteId) {
      try {
        deleteTutorial();
      } catch (err) {
        setDeleteResult(fortmatResponse(err));
      }
    }
  }

  const clearDeleteOutput = () => {
    setDeleteResult(null);
  };

  return (
    <div id="app" className="container">
      <div className="card">
        <div className="card-header">
          React Query Axios Typescript DELETE - BezKoder.com
        </div>
        <div className="card-body">
          <div className="input-group input-group-sm">
            <button className="btn btn-sm btn-danger" onClick={deleteAllData}>
              Delete All
            </button>

            <input
              type="text"
              value={deleteId}
              onChange={(e) => setDeleteId(e.target.value)}
              className="form-control ml-2"
              placeholder="Id"
            />
            <div className="input-group-append">
              <button
                className="btn btn-sm btn-danger"
                onClick={deleteDataById}
              >
                Delete by Id
              </button>
            </div>

            <button
              className="btn btn-sm btn-warning ml-2"
              onClick={clearDeleteOutput}
            >
              Clear
            </button>
          </div>

          {deleteResult && (
            <div className="alert alert-secondary mt-2" role="alert">
              <pre>{deleteResult}</pre>
            </div>
          )}
        </div>
      </div>
    </div>
  );
}

export default App;

The result could be like this:

react-query-axios-typescript-delete-example

Conclusion

With this React Query and Axios example in Typescript, you’ve known many ways to make GET/POST/PUT/DELETE request using react-query and axios library in a React Typescript component.

If you don’t want to use React Query, just Axios. Kindly visit
React Axios example – Get/Post/Put/Delete with Rest API

Happy Learning! See you again.

Source Code

The complete source code for this tutorial can be found at Github.

Further Reading

React Hook Form Typescript example with Validation
React Typescript and Axios (without React Query) with API call example
React Table example: CRUD App | react-table 7
React Hooks File Upload example with Axios & Progress Bar
React Typescript Authentication example with Hooks

Serverless with Firebase:
React Hooks + Firebase Realtime Database: CRUD App
React Hooks + Firestore example: CRUD app

Leave a Reply

Your email address will not be published. Required fields are marked *