DEV Community

Cover image for The Best Way To Integrate APIs In React JS
Sajith P J
Sajith P J

Posted on

The Best Way To Integrate APIs In React JS

Hii Developers.

How do you integrate the APIs in React JS? Is there any best way to integrate the APIs, which will reduce the line of code, and provide an easy method to handle errors?

Let me share an improved way to integrate the APIs in React JS.

Let's ExploreπŸ™Œ

Before We Start

Be ready with your React JS project with a form and install Yup to validate the form. Confused??, No worries.

Step 1: Installing React JS
I am going with vite to install the React JS. Simply run the following command to install React JS.

npm create vite@latest my-react-app --template react
Enter fullscreen mode Exit fullscreen mode

*replace the my-react-app with your project name.

change the directory to the project folder.

cd my-react-app
Enter fullscreen mode Exit fullscreen mode

Install the required dependencies by running

npm install
Enter fullscreen mode Exit fullscreen mode

Step 2: Install Axios
Install the Axios by running

npm i axios 
Enter fullscreen mode Exit fullscreen mode

Step 3: Install react-toastify
We are going to use react-toastify to handle the errors from backend.

npm i react-toastify
Enter fullscreen mode Exit fullscreen mode

You can ignore this step if you have your own way to display the error.
That's all about installations. Let's create a form.

Step 4: Setup a form with validation
I am creating the form inside App.jsx. You follow your folder structure.

import { useState } from "react";
import { removeError, validate } from "./utils/validation";
import { object, string } from "yup";
import { httpRequest } from "../../../config";

const App = () => {
  const [employeeDetails, setEmployeeDetails] = useState({
    name: "",
    email: "",
    phoneNumber: "",
    password: "",
  });

  const handleChange = (event) => {
    setEmployeeDetails({
      ...employeeDetails,
      [event.target.name]: event. target.value,
    });
    removeError(event.target.name)
  };

  // A yup validation schema to validate the form.
  const validationSchema = object().shape({
    name: string().required("Please enter you name"),
    email: string()
      .required("Email is required")
      .email("Please enter a valid email address"),
    phoneNumber: string()
      .matches(/^[0-9]{10}$/, "Phone number must be exactly 10 digits")
      .required("Phone number is required"),
    password: string()
      .required("Password is required")
      .min(8, "Password must be at least 8 characters long")
      .matches(/[a-z]/, "Password must contain at least one lowercase letter")
      .matches(/[A-Z]/, "Password must contain at least one uppercase letter")
      .matches(/[0-9]/, "Password must contain at least one number"),
  });

  const handleSubmit = async (event) => {
    event.preventDefault();
    // validate is a function which will validate the value against the 
    // validationSchema and generates the error in UI 
    const { errors, isValid } = await validate({
      validationSchema,
      value: employeeDetails,
    });

    if (!isValid) return;
     // apiCall()
  };

  return (
    <div className="flex items-center justify-center min-h-screen bg-gray-100">
      <div className="w-full max-w-md p-8 space-y-6 bg-white rounded shadow-lg">
        <h2 className="text-2xl font-bold text-center">Add Employee</h2>
        <form onSubmit={handleSubmit} className="space-y-4">
          <div data-input-parent="true">
            <label className="block text-sm font-medium">Name</label>
            <input
              type="text"
              name="name"
              id="name"
              value={employeeDetails.name}
              onChange={handleChange}
              className="w-full px-3 py-2 border rounded-lg focus:outline-none focus:ring focus:border-blue-300"
              data-input="true"
            />
          </div>

          <div data-input-parent="true">
            <label className="block text-sm font-medium">Email</label>
            <input
              type="email"
              name="email"
              id="email"
              value={employeeDetails.email}
              onChange={handleChange}
              className="w-full px-3 py-2 border rounded-lg focus:outline-none focus:ring focus:border-blue-300"
              data-input="true"
            />
          </div>

          <div data-input-parent="true">
            <label className="block text-sm font-medium">Phone Number</label>
            <input
              type="text"
              name="phoneNumber"
              id="phoneNumber"
              value={employeeDetails.phoneNumber}
              onChange={handleChange}
              data-input="true"
              className="w-full px-3 py-2 border rounded-lg focus:outline-none focus:ring focus:border-blue-300"
            />
          </div>

          <div data-input-parent="true">
            <label className="block text-sm font-medium">Password</label>
            <input
              type="password"
              name="password"
              id="password"
              value={employeeDetails.password}
              onChange={handleChange}
              data-input="true"
              className="w-full px-3 py-2 border rounded-lg focus:outline-none focus:ring focus:border-blue-300"
            />
          </div>

          <button
            type="submit"
            className="w-full px-4 py-2 text-white bg-blue-500 rounded-lg hover:bg-blue-700 focus:outline-none focus:ring focus:ring-blue-300"
          >
            Submit
          </button>
        </form>
      </div>
    </div>
  );
};

export default App;

Enter fullscreen mode Exit fullscreen mode

Cool😎, Now we have the form ready with us πŸ₯³.

IF YOU ARE USING THE ABOVE FORM AND ITS VALIDATION. PLEASE READ ABOUT THE VALIDATE METHOD USED TO VALIDATE THE FORM.

All Set!! Let's Start

First, you need to create the reusable function to make API calls, for that, you need to set up the Axios.

  • Create a folder called config inside src.
  • Create a file called index.js inside the folder called config.
  • Create a file called axios.js inside the folder called config.

So, the project's folder structure will be like this.

my-react-app/
β”œβ”€β”€ src/
β”‚   β”œβ”€β”€ config/
β”‚   β”‚   β”œβ”€β”€ axios.js
β”‚   β”‚   └── index.js
β”‚   β”œβ”€β”€ App.jsx
β”‚   β”œβ”€β”€ main.jsx
β”‚   β”œβ”€β”€ assets/
β”‚   β”œβ”€β”€ components/
β”‚   β”œβ”€β”€ pages/
β”‚   β”œβ”€β”€ styles/
β”œβ”€β”€ public/
β”œβ”€β”€ index.html
β”œβ”€β”€ package.json
β”œβ”€β”€ vite.config.js
Enter fullscreen mode Exit fullscreen mode

In axios.js, we will create axios instance and a interceptors to request and response.

import axios from "axios";

const axiosInstance = axios.create();
// Request Interceptor
axiosInstance.interceptors.request.use((config) => {
  // setting the base URL from env, example: https://api.example.com/api
  config.baseURL = import.meta.env.VITE_API_BASE_URL;
  // config.headers = Object.assign(
  //  {
  //    Authorization: `Bearer ${localStorage.getItem("token")}`,
  //  },
  //  config.headers
  // );
  return config;
});

//Example of Response Interceptor
axiosInstance.interceptors.response.use(
  (response) => {
    return response;
  },
  (err) => {
    return Promise.reject(err);
  }
);

export default axiosInstance;
Enter fullscreen mode Exit fullscreen mode

Here, We created a axios instance and stored in axiosInstance.
Request Interceptor: The request interceptor modifies the request configuration before the request is sent.

Base URL: config.baseURL is set from an environment variable (VITE_API_BASE_URL). This sets the base URL for all requests made with this Axios instance.

Authorization Header (Commented): There is a commented-out section that would add an Authorization header with a token from local storage. Uncommenting this code would attach the header to every request made by this Axios instance.

Response Interceptor: The response interceptor allows you to handle responses and errors globally.

  • Success Handler: The first function passed to
    axiosInstance.interceptors.response.use is the success handler. It
    receives the response object and returns it directly, meaning
    successful responses are passed through without modification.

  • Error Handler: The second function is the error handler. It
    receives the error object and returns a rejected Promise. This
    allows you to handle errors in a centralized place. You can add
    custom error handling logic here if needed.

Finally, we are exporting axiosInstance from this file.

Inside src/config/index.js we will add functions to make the API call and handle the response.

const getMessages = (status, message) => {
  if (message !== undefined && message !== "") {
    return message;
  }
  return "Something Went Wrong";
};

const handleResponse = (error) => {
// set the message according to your API response.
  let message =
    error.response?.data.message !== undefined
      ? error.response.data.message
      : "";
  let successStatusCode = [200];
  let errorStatusCode = [400, 500, 404, 403];
  let status = error.response?.status;

  if (errorStatusCode.includes(status)) {
    toast(getMessages(status, message), {
      type: "error",
      position: "top-right",
      autoClose: 2000,
      hideProgressBar: false,
      closeOnClick: true,
      pauseOnHover: true,
      draggable: true,
      progress: undefined,
      theme: "light",
    });
  } else if (status === 401) {
    window.location = "/signin";
  }
};

export const httpRequest = ({
  api,
  method = "get",
  data = {},
  config = {},
  catchError = true,
}) => {
  const httpMethod = method.toLocaleLowerCase();
  return new Promise((resolve, reject) => {
    axiosInstance[httpMethod](api, data, config)
      .then((response) => {
        resolve(response);
      })
      .catch((error) => {
        if (catchError) handleResponse(error);
        else reject(error);
      });
  });
};
Enter fullscreen mode Exit fullscreen mode

Explanation

In the config/index.js, I created tree functions httpRequest, handleResponse, and getMessages.

httpRequest: This function makes an HTTP request using the Axios instance and handles the response and errors. This function will accept some parameters like,

  • api: The API endpoint.
  • method: The HTTP method (default is "get").
  • data: The request payload (default is an empty object).
  • config: Additional Axios configuration (default is an empty object).
  • catchError: A boolean indicating whether to handle errors internally or reject the promise (default is true).

This function returns a new Promise. The function calls the appropriate Axios method (get, post, put, etc.) based on the lowercase HTTP method. If the request is successful, it resolves the promise with the response. If the request fails, it either handles the error using the handleResponse function or rejects the promise with the error, depending on the value of the catchError flag.

handleResponse:This function handles the API response errors. This function receives a parameter called error, its will be axios error object from catch.

  • Retrieves the message from the error response if it exists.
  • Defines successStatusCode and errorStatusCode arrays for reference.
  • Checks the status code of the response:
    • If the status code is in errorStatusCode, it displays a toast notification with the error message.
      • If the status code is 401, it redirects the user to the /signin page.

GoodπŸ™Œ, You just created a function that can be used for any project to integrate API.

How To Use With Form

So, the Form will look like this

import { useState } from "react";
import { removeError, validate } from "./utils/validation";
import { object, string } from "yup";
import { httpRequest } from "./config";

const App = () => {
  const [employeeDetails, setEmployeeDetails] = useState({
    name: "",
    email: "",
    phoneNumber: "",
    password: "",
  });

  const handleChange = (event) => {
    setEmployeeDetails({
      ...employeeDetails,
      [event.target.name]: event. target.value,
    });
    removeError(event.target.name)
  };

  // A yup validation schema to validate the form.
  const validationSchema = object().shape({
    name: string().required("Please enter you name"),
    email: string()
      .required("Email is required")
      .email("Please enter a valid email address"),
    phoneNumber: string()
      .matches(/^[0-9]{10}$/, "Phone number must be exactly 10 digits")
      .required("Phone number is required"),
    password: string()
      .required("Password is required")
      .min(8, "Password must be at least 8 characters long")
      .matches(/[a-z]/, "Password must contain at least one lowercase letter")
      .matches(/[A-Z]/, "Password must contain at least one uppercase letter")
      .matches(/[0-9]/, "Password must contain at least one number"),
  });

  const handleSubmit = async (event) => {
    event.preventDefault();
    // validate is a function which will validate the value against the 
    // validationSchema and generates the error in UI 
    const { errors, isValid } = await validate({
      validationSchema,
      value: employeeDetails,
    });

    if (!isValid) return;
      httpRequest({
      api: "/auth/signin",
      method: "post",
      data: employeeDetails,
    }).then((response) => {
      console.log("API, Success ");
    });
    // Dont need to add catch as we catch error in a common function.
  };

  return (
    <div className="flex items-center justify-center min-h-screen bg-gray-100">
      <div className="w-full max-w-md p-8 space-y-6 bg-white rounded shadow-lg">
        <h2 className="text-2xl font-bold text-center">Add Employee</h2>
        <form onSubmit={handleSubmit} className="space-y-4">
          <div data-input-parent="true">
            <label className="block text-sm font-medium">Name</label>
            <input
              type="text"
              name="name"
              id="name"
              value={employeeDetails.name}
              onChange={handleChange}
              className="w-full px-3 py-2 border rounded-lg focus:outline-none focus:ring focus:border-blue-300"
              data-input="true"
            />
          </div>

          <div data-input-parent="true">
            <label className="block text-sm font-medium">Email</label>
            <input
              type="email"
              name="email"
              id="email"
              value={employeeDetails.email}
              onChange={handleChange}
              className="w-full px-3 py-2 border rounded-lg focus:outline-none focus:ring focus:border-blue-300"
              data-input="true"
            />
          </div>

          <div data-input-parent="true">
            <label className="block text-sm font-medium">Phone Number</label>
            <input
              type="text"
              name="phoneNumber"
              id="phoneNumber"
              value={employeeDetails.phoneNumber}
              onChange={handleChange}
              data-input="true"
              className="w-full px-3 py-2 border rounded-lg focus:outline-none focus:ring focus:border-blue-300"
            />
          </div>

          <div data-input-parent="true">
            <label className="block text-sm font-medium">Password</label>
            <input
              type="password"
              name="password"
              id="password"
              value={employeeDetails.password}
              onChange={handleChange}
              data-input="true"
              className="w-full px-3 py-2 border rounded-lg focus:outline-none focus:ring focus:border-blue-300"
            />
          </div>

          <button
            type="submit"
            className="w-full px-4 py-2 text-white bg-blue-500 rounded-lg hover:bg-blue-700 focus:outline-none focus:ring focus:ring-blue-300"
          >
            Submit
          </button>
        </form>
      </div>
    </div>
  );
};

export default App;
Enter fullscreen mode Exit fullscreen mode

WooohhπŸ˜ƒ, You just learned the best method for API integration.

Conclusion

In the traditional method, you need to duplicate API call like axios.get() or axios.post() a lot of times. if you are dealing with 100 APIs in a project, there will be a lot of duplicated code. By the above method, we can ignore duplication and it will help you to catch errors globally.

About Me

I am Sajith P J, Senior React JS Developer, A JavaScript developer with the entrepreneurial mindset. I combine my experience with the super powers of JavaScript to satisfy your requirements.

_Reach Me Out _

Top comments (0)