Conventions Frontend¶
Conventions de développement React / TypeScript pour Primatch.
Règles fondamentales¶
| Règle | ✅ Correct | ❌ Incorrect |
|---|---|---|
| Exports | export const MyComponent = () => {} | export default MyComponent |
| Imports | import { foo } from '@/hooks/useGames' | import { foo } from '../../hooks/useGames' |
| Data fetching | const { data } = useGames() | useEffect(() => fetch(...), []) |
| Types | interface Game { ... } (pas any) | const game: any = ... |
| Variables unused | const _unusedVar = ... | Laisser une variable non utilisée |
Architecture des composants¶
Hiérarchie¶
pages/ → Assemblent les features et layouts (haut niveau)
└── components/features/ → Logique spécifique à un domaine
└── components/ui/ → Primitives réutilisables (sans logique métier)
Exemple de composition¶
// pages/games/GameDetailPage.tsx
export const GameDetailPage = () => {
const { id } = useParams();
const { data: game, isLoading } = useGame(Number(id));
if (isLoading) return <LoadingSpinner />;
return (
<PageWrapper title={`Partie — ${game?.date}`}>
<GameHeader game={game} /> {/* Feature component */}
<ScoreDisplay score={game?.score} /> {/* Feature component */}
<PlayersList players={game?.players} /> {/* Feature component */}
</PageWrapper>
);
};
Hooks React Query¶
Tous les appels API passent par des hooks dédiés dans hooks/ :
// hooks/useGames.ts
export const useGames = () => {
return useQuery({
queryKey: ['games'],
queryFn: () => gameService.getAll(),
});
};
// hooks/useCreateGame.ts
export const useCreateGame = () => {
const queryClient = useQueryClient();
return useMutation({
mutationFn: (dto: CreateGameDTO) => gameService.create(dto),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['games'] });
},
});
};
Formulaires (React Hook Form + Zod)¶
// Schéma Zod d'abord (source de vérité)
const createGameSchema = z.object({
type: z.enum(['friendly', 'competitive']),
scheduledAt: z.string().datetime(),
});
type CreateGameForm = z.infer<typeof createGameSchema>;
// Composant
export const CreateGameForm = () => {
const { register, handleSubmit, formState: { errors } } = useForm<CreateGameForm>({
resolver: zodResolver(createGameSchema),
});
const createGame = useCreateGame();
return (
<form onSubmit={handleSubmit((data) => createGame.mutate(data))}>
{/* fields */}
</form>
);
};
Internationalisation¶
// Toujours utiliser useTranslation pour les textes
import { useTranslation } from 'react-i18next';
export const GameCard = ({ game }: { game: Game }) => {
const { t } = useTranslation('games');
return <h2>{t('game.title', { date: game.date })}</h2>;
};
Les fichiers de traduction sont dans src/i18n/locales/{fr,en}/.