Smithery Logo
MCPsSkillsDocsPricing
Login
NewFlame, an assistant that learns and improves. Available onTelegramSlack
    feraudet

    react-conventions

    feraudet/react-conventions
    Coding

    About

    SKILL.md

    Install

    • Telegram
      Telegram
    • Slack
      Slack
    • Claude Code
      Claude Code
    • Codex
      Codex
    • OpenClaw
      OpenClaw
    • Cursor
      Cursor
    • Amp
      Amp
    • GitHub Copilot
      GitHub Copilot
    • Gemini CLI
      Gemini CLI
    • Kilo Code
      Kilo Code
    • Junie
      Junie
    • Replit
      Replit
    • Windsurf
      Windsurf
    • Cline
      Cline
    • Continue
      Continue
    • OpenCode
      OpenCode
    • OpenHands
      OpenHands
    • Roo Code
      Roo Code
    • Augment
      Augment
    • Goose
      Goose
    • Trae
      Trae
    • Zencoder
      Zencoder
    • Antigravity
      Antigravity
    • Download skill
    ├─
    ├─
    └─
    Smithery Logo

    Give agents more agency

    Resources

    DocumentationPrivacy PolicySystem Status

    Company

    PricingAboutBlog

    Connect

    © 2026 Smithery. All rights reserved.

    About

    Standards React et frontend. Use when "react component", "frontend", "ui", "hook", "jsx".

    SKILL.md

    React Conventions

    Purpose

    Définir les standards React pour le frontend du projet consultant-manager.

    Stack Frontend

    • Framework: React 18.3+
    • Build: Vite
    • Router: React Router 7
    • Styling: Tailwind CSS
    • Dates: date-fns
    • TypeScript: Strict mode

    Structure des Composants

    Anatomie d'un Composant

    // 1. Imports groupés
    import { useState, useEffect } from 'react';
    import { useNavigate } from 'react-router-dom';
    import { formatDate } from '../utils/format';
    import { consultantsAPI } from '../services/api';
    import type { Consultant } from '../types';
    
    // 2. Types/Interfaces
    interface ConsultantCardProps {
      consultant: Consultant;
      onEdit?: (consultant: Consultant) => void;
      onDelete?: (id: string) => void;
    }
    
    // 3. Composant
    export default function ConsultantCard({
      consultant,
      onEdit,
      onDelete
    }: ConsultantCardProps) {
      // 4. Hooks (dans l'ordre: state, effects, context, custom hooks)
      const [loading, setLoading] = useState(false);
      const navigate = useNavigate();
    
      useEffect(() => {
        // Side effects
      }, []);
    
      // 5. Event handlers
      const handleEdit = () => {
        onEdit?.(consultant);
      };
    
      const handleDelete = async () => {
        if (!confirm('Êtes-vous sûr ?')) return;
        setLoading(true);
        try {
          await consultantsAPI.delete(consultant.id);
          onDelete?.(consultant.id);
        } catch (error) {
          console.error(error);
        } finally {
          setLoading(false);
        }
      };
    
      // 6. Render conditions
      if (loading) {
        return <div>Chargement...</div>;
      }
    
      // 7. JSX
      return (
        <div className="bg-white shadow rounded-lg p-4">
          <h3 className="text-lg font-semibold">
            {consultant.prenom} {consultant.nom}
          </h3>
          <p className="text-gray-600">{consultant.email}</p>
          <div className="mt-4 flex gap-2">
            <button onClick={handleEdit} className="btn-primary">
              Modifier
            </button>
            <button onClick={handleDelete} className="btn-danger">
              Supprimer
            </button>
          </div>
        </div>
      );
    }
    

    Conventions de Nommage

    Fichiers

    • Composants: PascalCase.tsx (ex: ConsultantForm.tsx)
    • Hooks: useCamelCase.ts (ex: useConsultants.ts)
    • Utils: camelCase.ts (ex: formatDate.ts)
    • Pages: PascalCase.tsx (ex: Dashboard.tsx)

    Variables

    // Composants: PascalCase
    const ConsultantCard = () => {};
    
    // Fonctions: camelCase
    const handleSubmit = () => {};
    
    // Constantes: UPPER_SNAKE_CASE (si vraiment constantes)
    const MAX_FILE_SIZE = 5 * 1024 * 1024;
    
    // État: camelCase descriptif
    const [isLoading, setIsLoading] = useState(false);
    const [consultants, setConsultants] = useState<Consultant[]>([]);
    
    // Event handlers: handle{Action}
    const handleClick = () => {};
    const handleSubmit = () => {};
    const handleDelete = () => {};
    

    Hooks

    Ordre des Hooks

    function MyComponent() {
      // 1. State hooks
      const [data, setData] = useState<Data[]>([]);
      const [loading, setLoading] = useState(false);
      const [error, setError] = useState<string | null>(null);
    
      // 2. Router hooks
      const navigate = useNavigate();
      const params = useParams();
      const location = useLocation();
    
      // 3. Effect hooks
      useEffect(() => {
        loadData();
      }, []);
    
      // 4. Custom hooks
      const { user } = useAuth();
      const { consultants } = useConsultants();
    
      // ...
    }
    

    Custom Hooks

    // useConsultants.ts
    import { useState, useEffect } from 'react';
    import { consultantsAPI } from '../services/api';
    import type { Consultant } from '../types';
    
    export function useConsultants(filters?: { statut?: string }) {
      const [consultants, setConsultants] = useState<Consultant[]>([]);
      const [loading, setLoading] = useState(true);
      const [error, setError] = useState<string | null>(null);
    
      useEffect(() => {
        let isMounted = true;
    
        async function load() {
          try {
            setLoading(true);
            const data = await consultantsAPI.getAll(filters);
            if (isMounted) {
              setConsultants(data);
            }
          } catch (err) {
            if (isMounted) {
              setError(err instanceof Error ? err.message : 'Erreur');
            }
          } finally {
            if (isMounted) {
              setLoading(false);
            }
          }
        }
    
        load();
    
        return () => {
          isMounted = false;
        };
      }, [filters?.statut]);
    
      const refresh = () => {
        // Re-déclencher le useEffect
      };
    
      return { consultants, loading, error, refresh };
    }
    
    // Utilisation
    function ConsultantsList() {
      const { consultants, loading, error } = useConsultants({ statut: 'DISPONIBLE' });
    
      if (loading) return <div>Chargement...</div>;
      if (error) return <div>Erreur: {error}</div>;
    
      return <div>{/* ... */}</div>;
    }
    

    Gestion d'État

    État Local (useState)

    // Pour état simple, local à un composant
    const [count, setCount] = useState(0);
    const [isOpen, setIsOpen] = useState(false);
    const [formData, setFormData] = useState<FormData>({});
    

    État Dérivé (useMemo)

    // Calculer depuis les props/state
    const sortedConsultants = useMemo(() => {
      return consultants.sort((a, b) => a.nom.localeCompare(b.nom));
    }, [consultants]);
    
    const filteredMissions = useMemo(() => {
      return missions.filter(m => m.statutFacturation === 'PAYEE');
    }, [missions]);
    

    État de Formulaire

    function ConsultantForm({ consultant, onClose }: Props) {
      const [formData, setFormData] = useState({
        nom: consultant?.nom || '',
        prenom: consultant?.prenom || '',
        email: consultant?.email || '',
        tjm: consultant?.tjm || 0,
      });
    
      const handleChange = (field: string, value: any) => {
        setFormData(prev => ({ ...prev, [field]: value }));
      };
    
      const handleSubmit = async (e: FormEvent) => {
        e.preventDefault();
        // Valider et soumettre
      };
    
      return (
        <form onSubmit={handleSubmit}>
          <input
            value={formData.nom}
            onChange={(e) => handleChange('nom', e.target.value)}
          />
          {/* ... */}
        </form>
      );
    }
    

    Appels API

    Pattern Chargement/Erreur/Données

    function ConsultantsList() {
      const [consultants, setConsultants] = useState<Consultant[]>([]);
      const [loading, setLoading] = useState(true);
      const [error, setError] = useState<string | null>(null);
    
      useEffect(() => {
        loadConsultants();
      }, []);
    
      const loadConsultants = async () => {
        try {
          setLoading(true);
          setError(null);
          const data = await consultantsAPI.getAll();
          setConsultants(data);
        } catch (err) {
          setError(err instanceof Error ? err.message : 'Erreur de chargement');
        } finally {
          setLoading(false);
        }
      };
    
      if (loading) {
        return (
          <div className="flex items-center justify-center h-64">
            <div className="text-gray-500">Chargement...</div>
          </div>
        );
      }
    
      if (error) {
        return (
          <div className="bg-red-50 border border-red-200 rounded-lg p-4">
            <p className="text-red-800">{error}</p>
            <button onClick={loadConsultants} className="mt-2 btn-primary">
              Réessayer
            </button>
          </div>
        );
      }
    
      return (
        <div>
          {consultants.map(consultant => (
            <ConsultantCard key={consultant.id} consultant={consultant} />
          ))}
        </div>
      );
    }
    

    Tailwind CSS

    Principes

    • Utiliser les classes utilitaires uniquement
    • Pas de CSS custom (sauf cas exceptionnel)
    • Responsive design avec préfixes (sm:, md:, lg:)
    • Dark mode prévu avec dark: (si applicable)

    Patterns Communs

    Boutons:

    // Primary
    <button className="px-4 py-2 bg-indigo-600 text-white rounded-md hover:bg-indigo-700 disabled:opacity-50">
      Action
    </button>
    
    // Secondary
    <button className="px-4 py-2 border border-gray-300 rounded-md text-gray-700 hover:bg-gray-50">
      Annuler
    </button>
    
    // Danger
    <button className="px-4 py-2 bg-red-600 text-white rounded-md hover:bg-red-700">
      Supprimer
    </button>
    

    Cards:

    <div className="bg-white shadow rounded-lg p-6">
      {/* Contenu */}
    </div>
    

    Forms:

    <input
      type="text"
      className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-indigo-500 focus:border-indigo-500"
    />
    

    Badges:

    <span className="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-green-100 text-green-800">
      Disponible
    </span>
    

    Responsive

    <div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4">
      {/* 1 col mobile, 2 tablet, 4 desktop */}
    </div>
    

    Routing

    Routes

    // App.tsx
    import { BrowserRouter, Routes, Route } from 'react-router-dom';
    
    function App() {
      return (
        <BrowserRouter>
          <Routes>
            <Route path="/" element={<Layout />}>
              <Route index element={<Dashboard />} />
              <Route path="consultants" element={<Consultants />} />
              <Route path="missions" element={<Missions />} />
              <Route path="calendar" element={<Calendar />} />
            </Route>
          </Routes>
        </BrowserRouter>
      );
    }
    

    Navigation

    import { Link, useNavigate } from 'react-router-dom';
    
    function MyComponent() {
      const navigate = useNavigate();
    
      return (
        <>
          {/* Navigation déclarative */}
          <Link to="/consultants" className="text-indigo-600">
            Voir consultants
          </Link>
    
          {/* Navigation programmatique */}
          <button onClick={() => navigate('/consultants')}>
            Aller aux consultants
          </button>
        </>
      );
    }
    

    Performance

    Éviter Re-renders Inutiles

    // useMemo pour calculs coûteux
    const expensiveValue = useMemo(() => {
      return computeExpensiveValue(data);
    }, [data]);
    
    // useCallback pour fonctions stables
    const handleClick = useCallback(() => {
      doSomething(id);
    }, [id]);
    
    // React.memo pour composants
    const MemoizedComponent = React.memo(ExpensiveComponent);
    

    Lazy Loading

    import { lazy, Suspense } from 'react';
    
    const Calendar = lazy(() => import('./pages/Calendar'));
    
    function App() {
      return (
        <Suspense fallback={<div>Chargement...</div>}>
          <Calendar />
        </Suspense>
      );
    }
    

    Accessibilité

    // Labels explicites
    <label htmlFor="email" className="block text-sm font-medium">
      Email
    </label>
    <input id="email" type="email" />
    
    // Boutons avec texte ou aria-label
    <button aria-label="Fermer">
      <XIcon />
    </button>
    
    // Roles sémantiques
    <nav role="navigation">
      <ul role="list">
        <li><Link to="/">Accueil</Link></li>
      </ul>
    </nav>
    

    Erreurs Courantes à Éviter

    ❌ Mauvais

    // Mutation directe du state
    consultants.push(newConsultant);
    setConsultants(consultants);
    
    // Oubli de key dans liste
    {consultants.map(c => <Card consultant={c} />)}
    
    // Effet sans dépendances
    useEffect(() => {
      loadData(); // Re-run à chaque render!
    });
    
    // Handler inline
    <button onClick={() => handleClick(id)}>Click</button> // Re-créé à chaque render
    

    ✅ Bon

    // Immutabilité
    setConsultants(prev => [...prev, newConsultant]);
    
    // Keys uniques
    {consultants.map(c => <Card key={c.id} consultant={c} />)}
    
    // Dépendances correctes
    useEffect(() => {
      loadData();
    }, [filters]);
    
    // Handler stable
    const handleClick = useCallback(() => {
      doSomething(id);
    }, [id]);
    

    Checklist

    Avant de commiter du code React:

    • Composants < 300 lignes (extraire si plus grand)
    • Props typées avec TypeScript
    • Keys uniques sur les listes
    • useEffect avec dépendances correctes
    • Pas de state mutation directe
    • Loading/Error states gérés
    • Responsive design (Tailwind responsive classes)
    • Accessibilité de base (labels, aria)
    • Pas de console.log oubliés
    • Tests pour composants critiques

    Ressources

    • React Documentation
    • React Router
    • Tailwind CSS
    • date-fns
    Recommended Servers
    Databutton
    Databutton
    Docfork
    Docfork
    OpenZeppelin
    OpenZeppelin
    Repository
    feraudet/ssii
    Files