The Best Way To Integrate APIs In React JS
Tags: javascript, learning, beginners, tutorial
Published: 2024-05-28 20:32:02


**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
```
*replace the my-react-app with your project name.

change the directory to the project folder.
```
cd my-react-app
```
Install the required dependencies by running
```
npm install
```

**Step 2: Install Axios**
Install the Axios by running
```
npm i axios 
```

**Step 3: Install react-toastify**
We are going to use react-toastify to handle the errors from backend.
```
npm i react-toastify
```
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.

```jsx
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;

```
**Cool**😎, Now we have the form ready with us 🥳.

> **_IF YOU ARE USING THE ABOVE FORM AND ITS VALIDATION. PLEASE READ ABOUT THE [VALIDATE](https://dev.to/sajithpj/validation-with-yup-did-you-know-this-method--5h8) 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
```

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

```javascript
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;
```
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.

```javascript
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);
      });
  });
};
```

## **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
```jsx
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;
```

 
**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 _**
- [LinkedIn](https://www.linkedin.com/in/sajith-p-j/)
- [Instagram](https://www.instagram.com/dev_.sajith/)
- [Website](https://sajith.in/)
- [Email(sajithpjofficialme@gmail.com)](mailto:sajithpjofficialme@gmail.com)