smithery.ai

react-conventions

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

First seen Mar 28, 2026

Installation

$ npx skills add https://smithery.ai

Similar popular skills

Related neighbors and high-traction skills in the same topics — useful to compare before installing.

Also in this package

Other skills from smithery.ai · top by installs.

npx skills add https://smithery.ai

Browse all from smithery.ai

More details

Agent compatibility

Declared targets from SKILL.md / docs. Unmarked agents are not listed — the skill may still install via the CLI.

Claude Code Not declared
Cursor Not declared
Codex Not declared
GitHub Copilot Not declared
Windsurf Not declared
Gemini CLI Not declared
Cline Not declared
OpenCode Not declared

Skill metadata

Parsed from SKILL.md frontmatter.

Allowed toolsRead, Grep, Glob

Package contents

Files included with this skill beyond the listing page.

  • skill md SKILL.md 12,148 B
  • docs SUMMARY.md 114 B

History

  1. First seen on skills.sh
  2. First recorded snapshot · 2 installs

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