Aller au contenu

Gestion d'état — React Query + State local

Primatch utilise une stratégie claire de séparation du state serveur et du state UI.


Principe fondamental

State serveur  → TanStack React Query  (données API, cache)
State UI local → useState / useReducer  (modales, formulaires, thème)
State global   → React Context          (auth, i18n) — à utiliser avec parcimonie

Anti-pattern à éviter

Ne jamais stocker des données API dans un state local (useState) et les synchroniser manuellement. C'est le rôle de React Query.


React Query — Patterns

Query (lecture)

// hooks/useGame.ts
export const useGame = (id: number) => {
  return useQuery({
    queryKey: ['games', id],    // Clé de cache unique
    queryFn: () => gameService.getById(id),
    enabled: id > 0,              // Query conditionnelle
    staleTime: 5 * 60 * 1000,    // 5 min avant refetch
  });
};

Mutation (écriture)

// hooks/useUpdateScore.ts
export const useUpdateScore = (gameId: number) => {
  const queryClient = useQueryClient();

  return useMutation({
    mutationFn: (score: ScoreDTO) => gameService.submitScore(gameId, score),

    // Mutation optimiste : mise à jour immédiate de l'UI
    onMutate: async (newScore) => {
      await queryClient.cancelQueries({ queryKey: ['games', gameId] });
      const previous = queryClient.getQueryData(['games', gameId]);
      queryClient.setQueryData(['games', gameId], (old) => ({ ...old, score: newScore }));
      return { previous };
    },

    onError: (_err, _vars, context) => {
      // Rollback en cas d'erreur
      queryClient.setQueryData(['games', gameId], context?.previous);
    },

    onSettled: () => {
      queryClient.invalidateQueries({ queryKey: ['games', gameId] });
    },
  });
};

Clés de cache — Conventions

// Convention : tableau du général au spécifique
['games']                      // Liste de toutes les parties
['games', id]                  // Une partie spécifique
['games', id, 'players']       // Joueurs d'une partie
['user', 'profile']            // Profil utilisateur connecté

State UI local — Exemples

// Modal : state local, pas pour React Query
const [isModalOpen, setIsModalOpen] = useState(false);

// Formulaire : géré par React Hook Form, pas useState
const form = useForm<CreateGameForm>();

// Thème : React Context (lecture globale)
const { theme } = useTheme();