Standards for Data Fetching, API integration, and Asynchronous State Management.
A consistent API layer is crucial for preventing "spaghetti code" in asynchronous logic. This document outlines the standards for handling data fetching in this project.
fetch or axios.get directly in a component. Always use the configured HTTP client.useGetUser), not Promises (api.getUser()).src/
├── lib/
│ └── apiClient.ts # Axios/Fetch instance with interceptors
├── features/
│ └── [feature]/
│ └── api/
│ ├── endpoints.ts # Raw API call functions (returns Promise<T>)
│ ├── queries.ts # React Query "read" hooks
│ └── mutations.ts # React Query "write" hooks
lib/apiClient.ts)This is the single source of truth for base URLs, timeouts, and auth headers.
import axios from 'axios';
export const apiClient = axios.create({
baseURL: process.env.NEXT_PUBLIC_API_URL,
headers: { 'Content-Type': 'application/json' },
});
apiClient.interceptors.response.use(
(response) => response.data,
(error) => {
// Global Error Handling (e.g., redirect on 401)
return Promise.reject(error);
}
);
api/endpoints.ts)Raw functions that return Promises. No React logic here.
import { apiClient } from '@/lib/apiClient';
import { User } from '../types';
export const fetchUser = (id: string): Promise<User> => {
return apiClient.get(`/users/${id}`);
};
export const updateUser = (id: string, data: Partial<User>): Promise<User> => {
return apiClient.put(`/users/${id}`, data);
};
api/queries.ts / mutations.ts)Wrappers around libraries like React Query (TanStack Query) or SWR.
// queries.ts
import { useQuery } from '@tanstack/react-query';
import { fetchUser } from './endpoints';
export const useUserQuery = (id: string) => {
return useQuery({
queryKey: ['user', id],
queryFn: () => fetchUser(id),
enabled: !!id, // Dependent query pattern
});
};
// mutations.ts
import { useMutation, useQueryClient } from '@tanstack/react-query';
import { updateUser } from './endpoints';
export const useUpdateUserMutation = () => {
const queryClient = useQueryClient();
return useMutation({
mutationFn: ({ id, data }) => updateUser(id, data),
onSuccess: (_, variables) => {
queryClient.invalidateQueries(['user', variables.id]);
}
});
};
The Component Logic Layer (MyComponent.hook.ts) integrates these hooks patterns.
// MyComponent.hook.ts
import { useUserQuery } from '../../api/queries';
import { useUpdateUserMutation } from '../../api/mutations';
export const useMyComponent = (userId: string) => {
const { data: user, isLoading } = useUserQuery(userId);
const { mutate, isLoading: isSaving } = useUpdateUserMutation();
const handleSave = (newName: string) => {
if (user) {
mutate({ id: user.id, data: { name: newName } });
}
};
return { user, isLoading, isSaving, handleSave };
};
isError and error flags from hooks to show feedback.