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.
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 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
insidesrc
. - Create a file called
index.js
inside the folder calledconfig
. - Create a file called
axios.js
inside the folder calledconfig
.
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.
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.
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.
- If the status code is 401, it redirects the user to the
- If the status code is in
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;
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.
Top comments (0)