API Layer Pattern¶
Organizing API calls with TanStack Query.
Structure¶
src/api/{feature}/
├── keys.ts # Query key factories
├── queries.ts # useQuery hooks
└── mutations.ts # useMutation hooks
Query Keys¶
export const markerKeys = {
all: ["markers"] as const,
lists: () => [...markerKeys.all, "list"] as const,
list: (filters: MarkerFilters) => [...markerKeys.lists(), filters] as const,
details: () => [...markerKeys.all, "detail"] as const,
detail: (id: string) => [...markerKeys.details(), id] as const,
};
Query Hook¶
export const useMarkers = (filters: MarkerFilters) =>
useQuery({
queryKey: markerKeys.list(filters),
queryFn: () => apiCall<MarkerResponse[]>(
getApiV1Markers({ query: filters })
),
});
export const useMarker = (id: string) =>
useQuery({
queryKey: markerKeys.detail(id),
queryFn: () => apiCall<MarkerResponse>(
getApiV1MarkersId({ path: { id } })
),
enabled: !!id,
});
Mutation Hook¶
export const useCreateMarker = () => {
const queryClient = useQueryClient();
return useMutation({
mutationFn: (data: CreateMarkerRequest) =>
apiCall<MarkerResponse>(postApiV1Markers({ body: data })),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: markerKeys.lists() });
},
meta: { invalidateOnConflict: [markerKeys.all] },
});
};
API Call Wrapper¶
export const apiCall = async <T>(promise: Promise<Response>): Promise<T> => {
const response = await promise;
if (!response.ok) {
throw new HttpError(response.status, await response.json());
}
return response.json();
};
Error Handling¶
const mutation = useCreateMarker();
mutation.mutate(data, {
onError: (error) => {
if (error instanceof HttpError) {
if (error.status === 409) {
queryClient.invalidateQueries({ queryKey: markerKeys.all });
}
showErrorToast(error.message);
}
},
});
Related¶
- State Management — Zustand stores
- Forms — Form handling