Intro
There’s no standard way of handling API requests, some do it in the component and some place the functions in utility modules. I am a fan of the latter because it scales better.
My Process
Define types - TS specific
In a folder like src/types/typeOne.ts
create a Interface for the returned data type.
Example:
// types/User.ts
export interface User {
id: number;
name: string;
email: string;
}
API Utility Modules
Similarly, bundle your API utils in a single folder, something along the lines of src/utils/api/userReq.ts
import { User } from '../types/User';
export const fetchUser = async (id: number): Promise<User> => {
const response = await fetch(`/api/users/${id}`);
if (!response.ok) {
throw new Error('Failed to fetch user');
}
return response.json();
};
or this real JS example from a project of mine:
export async function getUserProfile(){
const response = await fetch(`${SERVER_BASE_URL}/users/profile/`, {
credentials: "include",
method: "GET",
headers: {
"Content-Type": "application/json",
},
});
if (!response.ok) {
throw new Error("Failed to fetch user profile");
}
const info = await response.json();
return info.data;
}
Using the API Util
In your React component, you can define a function in your component that invokes the util using a try-catch
and that function is then used wherever it’s needed. E.g. in your useEffect
on initial mount and rendering.
Example:
const checkAdmin = async () => {
setLoading(true);
setError(null);
try {
const response = await fetch("https://api.clickclack.aabuharrus.dev/api/v1/auth/me"
, {
method: "GET",
headers: {
"Content-Type": "application/json",
},
credentials: 'include'
});
if (!response.ok) {
throw new Error("Failed to authenticate.");
}
const authRes = await response.json();
setIsAdmin(authRes.data.isAdmin);
setLoggedin(authRes.data.loggedin)
} catch (error) {
console.error("Error:", error);
setError(error.message);
} finally {
console.log("You're at home, Harry! 🏠")
setLoading(false)
}
}
useEffect(() => {
checkAdmin();
}, []);
Another Process
Some prefer to write their own hooks. So after you’ve defined the utilities, create a folder for your hooks. Example:
// hooks/useUser.ts
import { useEffect, useState } from 'react';
import { fetchUser } from '../api/userApi';
import { User } from '../types/User';
export const useUser = (id: number) => {
const [user, setUser] = useState<User | null>(null);
const [loading, setLoading] = useState<boolean>(true);
const [error, setError] = useState<string | null>(null);
useEffect(() => {
setLoading(true);
fetchUser(id)
.then(setUser)
.catch((err) => setError(err.message))
.finally(() => setLoading(false));
}, [id]);
return { user, loading, error };
};
// Full disclosure, I've not tried this process before and haven't made custom hooks.