Create API Hook¶
Crée un hook React Query pour récupérer des données depuis l'API.
Arguments¶
endpoint(requis) : L'endpoint à utiliser (depuis@bricks-common/api-communication)queryKey(requis) : La clé de query (depuisallStaticQueryKeys)
Instructions¶
- Demander à l'utilisateur :
- Le queryKey est-il statique ou dynamique (avec paramètres) ?
- Est-ce une query simple ou paginée (infinite query) ?
-
Si le validator n'est pas évident par le nom du hook : quel validator utiliser ?
- Un endpoint existant (
get{{EndpointName}}Endpoint.response) - Un validator custom à créer
- Un endpoint existant (
-
Déterminer le chemin :
-
src/api/{feature}/use{HookName}.ts -
Créer le hook selon le pattern approprié.
Architecture¶
Le data fetching a deux étapes de validation (gérées automatiquement par useValidatedQuery / useInfiniteValidatedQuery) :
processHttpResult(dansqueryFn) - Valide la réponse API, stocke les données brutes en cacheselect(interne) - Re-valide les données du cache, reconstruit les types riches (Cents, NanoCents, Date)
Templates¶
Hook Simple (QueryKey Statique)¶
import { get{{EndpointName}}Endpoint } from "@bricks-common/api-communication";
import { allStaticQueryKeys } from "@providers/ReactQueryProvider";
import { useValidatedQuery } from "@utils/api/useValidatedQuery";
import { customAxios } from "@utils/axios/axios";
export const use{{HookName}} = () =>
useValidatedQuery({
queryKey: allStaticQueryKeys.{{queryKeyName}},
validator: get{{EndpointName}}Endpoint.response,
queryFn: () => customAxios.get(get{{EndpointName}}Endpoint.request.path),
});
Hook Simple (QueryKey Dynamique)¶
import { get{{EndpointName}}Endpoint, type UUID } from "@bricks-common/api-communication";
import { allStaticQueryKeys } from "@providers/ReactQueryProvider";
import { useValidatedQuery } from "@utils/api/useValidatedQuery";
import { customAxios } from "@utils/axios/axios";
export const use{{HookName}} = (id: UUID) =>
useValidatedQuery({
queryKey: allStaticQueryKeys.{{queryKeyName}}(id),
validator: get{{EndpointName}}Endpoint.response,
queryFn: () => customAxios.get(get{{EndpointName}}Endpoint.request.path(id)),
});
Hook Simple avec Options¶
import { get{{EndpointName}}Endpoint } from "@bricks-common/api-communication";
import { allStaticQueryKeys } from "@providers/ReactQueryProvider";
import { useValidatedQuery } from "@utils/api/useValidatedQuery";
import { customAxios } from "@utils/axios/axios";
export const use{{HookName}} = (params?: { enabled?: boolean }) =>
useValidatedQuery({
queryKey: allStaticQueryKeys.{{queryKeyName}},
validator: get{{EndpointName}}Endpoint.response,
queryFn: () => customAxios.get(get{{EndpointName}}Endpoint.request.path),
showErrorNotification: false,
enabled: params?.enabled,
retry: 0,
});
Hook Infinite Query (Pagination)¶
import { get{{EndpointName}}Endpoint, type {{ParamsType}}, type {{ResponseType}} } from "@bricks-common/api-communication";
import { allStaticQueryKeys } from "@providers/ReactQueryProvider";
import { useInfiniteValidatedQuery } from "@utils/api/useInfiniteValidatedQuery";
import { customAxios } from "@utils/axios/axios";
const PAGE_SIZE = 10;
const getNextPageParam = (lastPage: {{ResponseType}}, allPages: {{ResponseType}}[]) => {
if (!Array.isArray(lastPage.data) || lastPage.data.length < PAGE_SIZE) {
return undefined;
}
return allPages.length * PAGE_SIZE;
};
export const use{{HookName}} = (filters: {{ParamsType}}) =>
useInfiniteValidatedQuery({
queryKey: allStaticQueryKeys.{{queryKeyName}}(filters),
validator: get{{EndpointName}}Endpoint.response,
queryFn: ({ pageParam }) =>
customAxios.get(get{{EndpointName}}Endpoint.request.path, {
params: { ...filters, cursor: pageParam, take: PAGE_SIZE },
}),
initialPageParam: 0,
getNextPageParam,
});
Hook Infinite Query avec Select Custom¶
import { get{{EndpointName}}Endpoint, type {{ParamsType}}, type {{ResponseType}} } from "@bricks-common/api-communication";
import { allStaticQueryKeys } from "@providers/ReactQueryProvider";
import { useInfiniteValidatedQuery } from "@utils/api/useInfiniteValidatedQuery";
import { customAxios } from "@utils/axios/axios";
const PAGE_SIZE = 10;
const getNextPageParam = (lastPage: {{ResponseType}}, allPages: {{ResponseType}}[]) => {
if (!Array.isArray(lastPage.data) || lastPage.data.length < PAGE_SIZE) {
return undefined;
}
return allPages.length * PAGE_SIZE;
};
export const use{{HookName}} = (filters: {{ParamsType}}) =>
useInfiniteValidatedQuery({
queryKey: allStaticQueryKeys.{{queryKeyName}}(filters),
validator: get{{EndpointName}}Endpoint.response,
queryFn: ({ pageParam }) =>
customAxios.get(get{{EndpointName}}Endpoint.request.path, {
params: { ...filters, cursor: pageParam, take: PAGE_SIZE },
}),
initialPageParam: 0,
getNextPageParam,
// select reçoit les pages déjà validées (TPage[])
select: (validatedPages) => ({
pages: validatedPages,
flatData: validatedPages.flatMap((page) => page.data),
}),
});
Options Disponibles¶
useValidatedQuery¶
| Option | Type | Description |
|---|---|---|
queryKey |
QueryKey |
Clé de cache React Query |
validator |
Validator<T> |
Validator idonttrustlikethat |
queryFn |
() => Promise<AxiosResponse> |
Fonction qui retourne la réponse Axios |
showErrorNotification |
boolean |
Afficher un toast en cas d'erreur (défaut: true) |
enabled, retry, staleTime, etc. |
- | Toutes les options standard de useQuery |
useInfiniteValidatedQuery¶
| Option | Type | Description |
|---|---|---|
queryKey |
QueryKey |
Clé de cache React Query |
validator |
Validator<TPage> |
Validator pour chaque page |
queryFn |
({ pageParam }) => Promise<AxiosResponse> |
Fonction qui retourne la réponse Axios |
getNextPageParam |
(lastPage, allPages) => number \| undefined |
Calcul du prochain curseur |
initialPageParam |
number |
Curseur initial (généralement 0) |
showErrorNotification |
boolean |
Afficher un toast en cas d'erreur (défaut: true) |
select |
(validatedPages: TPage[]) => TSelect |
Transformation des pages validées |
Cas Spéciaux¶
Hook avec Logique Conditionnelle (useQuery direct)¶
Pour les hooks qui nécessitent une logique conditionnelle complexe (ex: retourner null sans appel API), utiliser useQuery directement avec createSelectValidator :
import { userValidator } from "@bricks-common/api-communication";
import { useQuery } from "@tanstack/react-query";
import { allStaticQueryKeys } from "@providers/ReactQueryProvider";
import { processHttpResult } from "@utils/api/processHttpResult";
import { createSelectValidator } from "@utils/api/selectValidator";
import { customAxios } from "@utils/axios/axios";
const selectUser = createSelectValidator(userValidator, allStaticQueryKeys.getMe);
export const useMe = () =>
useQuery({
queryKey: allStaticQueryKeys.getMe,
queryFn: () =>
processHttpResult({
responsePromise: customAxios.get("/customers/me"),
validator: userValidator,
}),
select: selectUser,
enabled: !!token, // Conditionnel
});
Usage dans les Composants¶
const { data, isPending, isError } = useMyData();
// Quand isPending || isError → afficher skeleton/erreur
// Quand les deux sont false → data est garanti défini (TypeScript le sait)
{isPending || isError ? (
<Skeleton />
) : (
<Content data={data} /> // data est T, pas T | undefined
)}
Checklist finale¶
- [ ] Utiliser
useValidatedQueryouuseInfiniteValidatedQuery(sauf cas spécial) - [ ]
queryFnretourneAxiosResponse(pas le résultat deprocessHttpResult) - [ ] QueryKey ajouté dans
allStaticQueryKeyssi nouveau - [ ]
showErrorNotification: falsesi l'erreur est gérée autrement
Règles appliquées¶
- front-api-data-fetching.mdc
- front-state-management.mdc