Debutant 9 min de lecture · 1 833 mots

Introduction à React : Composants, Props, et State

Estimated reading time: 9 minutes

Pourquoi React ?

React est la bibliothèque JavaScript la plus populaire pour construire des interfaces utilisateur. En 2025, elle domine avec plus de 15 millions de téléchargements hebdomadaires sur NPM.

Avantages clés

  • Composants réutilisables : Créez une fois, utilisez partout
  • Virtual DOM : Mises à jour ultra-rapides
  • Écosystème riche : Milliers de bibliothèques compatibles
  • TypeScript natif : Support de premier ordre
  • React 18+ : Concurrent rendering et Suspense
  • 1. Premiers pas : Créer un projet React

    Avec Vite (Recommandé en 2025)

# Créer un nouveau projet
npm create vite@latest mon-app-react -- --template react-ts

# Naviguer et installer
cd mon-app-react
npm install

# Lancer le serveur de développement
npm run dev

Structure du projet

mon-app-react/
├── node_modules/
├── public/
├── src/
│   ├── App.tsx
│   ├── App.css
│   ├── main.tsx
│   └── vite-env.d.ts
├── index.html
├── package.json
├── tsconfig.json
└── vite.config.ts

2. Composants : Les briques de votre application

Votre premier composant

// src/components/Welcome.tsx
const Welcome = () => {
  return (
    
# Bienvenue dans React !

Votre premier composant fonctionne.

); }; export default Welcome;

JSX : JavaScript + XML

// JSX permet d'écrire du HTML dans JavaScript
const element = # Hello, world!;

// Équivalent JavaScript pur (ne pas utiliser!)
const element = React.createElement('h1', null, 'Hello, world!');

// Expressions JavaScript dans JSX
const name = 'Marie';
const element = # Bonjour, {name}!;

// Attributs en camelCase
const button = (
  
);

Composant avec structure complète

// src/components/UserProfile.tsx
import { CSSProperties } from 'react';

interface UserProfileProps {
  name: string;
  age: number;
  avatar?: string;
}

const UserProfile = ({ name, age, avatar }: UserProfileProps) => {
  const cardStyle: CSSProperties = {
    border: '1px solid #ddd',
    borderRadius: '8px',
    padding: '20px',
    maxWidth: '300px',
    textAlign: 'center'
  };

  return (
    
{avatar && ( {name} )} ## {name}

Age: {age} ans

); }; export default UserProfile;

Utiliser votre composant

// src/App.tsx
import UserProfile from './components/UserProfile';

function App() {
  return (
    
# Notre équipe
); } export default App;

3. Props : Passer des données aux composants

Props de base

// Button.tsx
interface ButtonProps {
  text: string;
  variant?: 'primary' | 'secondary' | 'danger';
  disabled?: boolean;
  onClick?: () => void;
}

const Button = ({
  text,
  variant = 'primary',
  disabled = false,
  onClick
}: ButtonProps) => {
  return (
    }
      disabled={disabled}
      onClick={onClick}
    >
      {text}
    
  );
};

// Utilisation



Props.children : Contenu imbriqué

// Card.tsx
interface CardProps {
  title: string;
  children: React.ReactNode;
  footer?: React.ReactNode;
}

const Card = ({ title, children, footer }: CardProps) => {
  return (
    
### {title}
{children}
{footer && (
{footer}
)}
); }; // Utilisation } >

Nom: Sophie Dupont

Email: sophie@example.com

Props avec spread operator

// Input.tsx
interface InputProps extends React.InputHTMLAttributes {
  label: string;
  error?: string;
}

const Input = ({ label, error, ...inputProps }: InputProps) => {
  return (
    
{error && {error}}
); }; // Utilisation

4. State : Données dynamiques et interactivité

useState : Le hook fondamental

import { useState } from 'react';

const Counter = () => {
  // [valeur, fonction pour modifier la valeur]
  const [count, setCount] = useState(0);

  const increment = () => setCount(count + 1);
  const decrement = () => setCount(count - 1);
  const reset = () => setCount(0);

  return (
    
## Compteur: {count}
); };

State avec types complexes

interface Todo {
  id: number;
  text: string;
  completed: boolean;
}

const TodoList = () => {
  const [todos, setTodos] = useState([]);
  const [inputValue, setInputValue] = useState('');

  const addTodo = () => {
    if (inputValue.trim()) {
      const newTodo: Todo = {
        id: Date.now(),
        text: inputValue,
        completed: false
      };
      setTodos([...todos, newTodo]);
      setInputValue('');
    }
  };

  const toggleTodo = (id: number) => {
    setTodos(todos.map(todo =>
      todo.id === id ? { ...todo, completed: !todo.completed } : todo
    ));
  };

  const deleteTodo = (id: number) => {
    setTodos(todos.filter(todo => todo.id !== id));
  };

  return (
    
## Ma Todo List
setInputValue(e.target.value)} onKeyPress={(e) => e.key === 'Enter' && addTodo()} placeholder="Nouvelle tâche..." />
    {todos.map(todo => (
  • toggleTodo(todo.id)} /> {todo.text}
  • ))}

{todos.filter(t => !t.completed).length} tâches restantes

); };

State avec objets

interface FormData {
  username: string;
  email: string;
  age: number;
  newsletter: boolean;
}

const RegistrationForm = () => {
  const [formData, setFormData] = useState({
    username: '',
    email: '',
    age: 18,
    newsletter: false
  });

  // ✅ Bonne pratique : fonction de mise à jour
  const updateField = (field: keyof FormData, value: any) => {
    setFormData(prev => ({
      ...prev,
      [field]: value
    }));
  };

  const handleSubmit = (e: React.FormEvent) => {
    e.preventDefault();
    console.log('Données du formulaire:', formData);
    // Envoyer à l'API...
  };

  return (
    
updateField('username', e.target.value)} placeholder="Nom d'utilisateur" /> updateField('email', e.target.value)} placeholder="Email" /> updateField('age', parseInt(e.target.value))} />
); };

5. Événements et handlers

Événements courants

const EventExamples = () => {
  // Click
  const handleClick = (e: React.MouseEvent) => {
    console.log('Cliqué!', e.currentTarget);
  };

  // Input change
  const handleChange = (e: React.ChangeEvent) => {
    console.log('Valeur:', e.target.value);
  };

  // Form submit
  const handleSubmit = (e: React.FormEvent) => {
    e.preventDefault();
    console.log('Formulaire soumis');
  };

  // Key press
  const handleKeyPress = (e: React.KeyboardEvent) => {
    if (e.key === 'Enter') {
      console.log('Enter pressé');
    }
  };

  // Focus
  const handleFocus = (e: React.FocusEvent) => {
    e.target.select(); // Sélectionne tout le texte
  };

  return (
    
); };

Passer des paramètres aux handlers

const ProductList = () => {
  const products = [
    { id: 1, name: 'Laptop', price: 999 },
    { id: 2, name: 'Mouse', price: 29 },
    { id: 3, name: 'Keyboard', price: 79 }
  ];

  // Méthode 1: Arrow function inline (simple mais crée une nouvelle fonction à chaque render)
  const handleBuy1 = (productId: number) => {
    console.log('Achat produit:', productId);
  };

  // Méthode 2: Currying (plus performant)
  const handleBuy2 = (productId: number) => () => {
    console.log('Achat produit:', productId);
  };

  return (
    
{products.map(product => (
### {product.name}

{product.price}€

{/ Méthode 1 /} {/ Méthode 2 (préférée) /}
))}
); };

6. Listes et clés

Rendu de listes

interface User {
  id: number;
  name: string;
  email: string;
  active: boolean;
}

const UserList = () => {
  const users: User[] = [
    { id: 1, name: 'Alice', email: 'alice@example.com', active: true },
    { id: 2, name: 'Bob', email: 'bob@example.com', active: false },
    { id: 3, name: 'Charlie', email: 'charlie@example.com', active: true }
  ];

  // ✅ Bonne pratique : key unique et stable
  return (
    
    {users.map(user => (
  • {user.name} {user.email}
  • ))}
); }; // ❌ Mauvaise pratique : index comme key users.map((user, index) => (
  • {user.name}
  • // Problèmes avec réorganisation )); // ❌ Mauvaise pratique : pas de key users.map(user => (
  • {user.name}
  • // Warning React ));

    Listes filtrées et triées

    const AdvancedUserList = () => {
      const [users, setUsers] = useState([...]);
      const [filter, setFilter] = useState<'all' | 'active' | 'inactive'>('all');
      const [sortBy, setSortBy] = useState<'name' | 'email'>('name');
    
      // Filtrage
      const filteredUsers = users.filter(user => {
        if (filter === 'active') return user.active;
        if (filter === 'inactive') return !user.active;
        return true;
      });
    
      // Tri
      const sortedUsers = [...filteredUsers].sort((a, b) => {
        return a[sortBy].localeCompare(b[sortBy]);
      });
    
      return (
        
      {sortedUsers.map(user => (
    • {user.name} - {user.email}
    • ))}

    Total: {sortedUsers.length} utilisateurs

    ); };

    7. Rendu conditionnel

    Méthodes de rendu conditionnel

    const ConditionalRendering = () => {
      const [isLoggedIn, setIsLoggedIn] = useState(false);
      const [user, setUser] = useState(null);
      const [status, setStatus] = useState<'loading' | 'success' | 'error'>('loading');
    
      // Méthode 1: if/else
      if (!isLoggedIn) {
        return ;
      }
    
      // Méthode 2: Ternaire
      return (
        
    {isLoggedIn ? ( ) : ( )}
    ); // Méthode 3: && (pour un seul cas) return (
    {user && } {error && }
    ); // Méthode 4: Switch avec objet const statusComponents = { loading: , success: , error: }; return (
    {statusComponents[status]}
    ); };

    8. Exemple complet : Application de recherche

    // SearchApp.tsx
    import { useState, useEffect } from 'react';
    
    interface Article {
      id: number;
      title: string;
      excerpt: string;
      category: string;
      tags: string[];
    }
    
    const SearchApp = () => {
      const [articles, setArticles] = useState
    ([]); const [searchTerm, setSearchTerm] = useState(''); const [selectedCategory, setSelectedCategory] = useState('all'); const [loading, setLoading] = useState(true); // Simuler un fetch API useEffect(() => { const fetchArticles = async () => { setLoading(true); // Dans la vraie vie: await fetch('/api/articles') const mockArticles: Article[] = [ { id: 1, title: 'Introduction à React', excerpt: 'Apprenez les bases de React', category: 'Tutorial', tags: ['react', 'javascript'] }, { id: 2, title: 'Next.js pour les débutants', excerpt: 'Créez des apps performantes', category: 'Tutorial', tags: ['nextjs', 'ssr'] }, { id: 3, title: 'TypeScript Best Practices', excerpt: 'Écrivez du TypeScript propre', category: 'Guide', tags: ['typescript', 'best-practices'] } ]; setTimeout(() => { setArticles(mockArticles); setLoading(false); }, 1000); }; fetchArticles(); }, []); // Filtrage const filteredArticles = articles.filter(article => { const matchesSearch = article.title.toLowerCase().includes(searchTerm.toLowerCase()) || article.excerpt.toLowerCase().includes(searchTerm.toLowerCase()); const matchesCategory = selectedCategory === 'all' || article.category === selectedCategory; return matchesSearch && matchesCategory; }); // Extraire les catégories uniques const categories = ['all', ...new Set(articles.map(a => a.category))]; if (loading) { return
    Chargement...
    ; } return (
    # Recherche d'articles
    setSearchTerm(e.target.value)} className="search-input" />

    {filteredArticles.length} résultat(s)

    {filteredArticles.length === 0 ? (
    Aucun article trouvé pour "{searchTerm}"
    ) : (
    {filteredArticles.map(article => (
    ## {article.title}

    {article.excerpt}

    {article.category}
    {article.tags.map(tag => ( #{tag} ))}
    ))}
    )}
    ); }; export default SearchApp;

    9. Tests avec React Testing Library

    // SearchApp.test.tsx
    import { render, screen, fireEvent, waitFor } from '@testing-library/react';
    import userEvent from '@testing-library/user-event';
    import SearchApp from './SearchApp';
    
    describe('SearchApp', () => {
      test('affiche le loading initialement', () => {
        render();
        expect(screen.getByText('Chargement...')).toBeInTheDocument();
      });
    
      test('affiche les articles après chargement', async () => {
        render();
    
        await waitFor(() => {
          expect(screen.getByText('Introduction à React')).toBeInTheDocument();
        });
      });
    
      test('filtre les articles par recherche', async () => {
        const user = userEvent.setup();
        render();
    
        await waitFor(() => {
          expect(screen.getByText('Introduction à React')).toBeInTheDocument();
        });
    
        const searchInput = screen.getByPlaceholderText('Rechercher un article...');
        await user.type(searchInput, 'Next.js');
    
        expect(screen.getByText('Next.js pour les débutants')).toBeInTheDocument();
        expect(screen.queryByText('Introduction à React')).not.toBeInTheDocument();
      });
    
      test('filtre par catégorie', async () => {
        render();
    
        await waitFor(() => {
          expect(screen.getByText('Introduction à React')).toBeInTheDocument();
        });
    
        const categorySelect = screen.getByRole('combobox');
        fireEvent.change(categorySelect, { target: { value: 'Guide' } });
    
        expect(screen.getByText('TypeScript Best Practices')).toBeInTheDocument();
        expect(screen.queryByText('Introduction à React')).not.toBeInTheDocument();
      });
    });
    

    10. Styles CSS

    / SearchApp.css /
    .search-app {
      max-width: 1200px;
      margin: 0 auto;
      padding: 20px;
      font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
    }
    
    .filters {
      display: flex;
      gap: 16px;
      margin: 24px 0;
    }
    
    .search-input,
    .category-select {
      padding: 12px 16px;
      border: 2px solid #e2e8f0;
      border-radius: 8px;
      font-size: 16px;
      transition: border-color 0.2s;
    }
    
    .search-input {
      flex: 1;
    }
    
    .search-input:focus,
    .category-select:focus {
      outline: none;
      border-color: #3b82f6;
    }
    
    .articles-grid {
      display: grid;
      grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
      gap: 24px;
      margin-top: 24px;
    }
    
    .article-card {
      padding: 24px;
      border: 1px solid #e2e8f0;
      border-radius: 12px;
      background: white;
      transition: transform 0.2s, box-shadow 0.2s;
    }
    
    .article-card:hover {
      transform: translateY(-4px);
      box-shadow: 0 12px 24px rgba(0, 0, 0, 0.1);
    }
    
    .article-card h2 {
      margin: 0 0 12px 0;
      color: #1a202c;
    }
    
    .meta {
      display: flex;
      justify-content: space-between;
      align-items: center;
      margin-top: 16px;
    }
    
    .category {
      padding: 4px 12px;
      background: #e0e7ff;
      color: #4c51bf;
      border-radius: 12px;
      font-size: 14px;
    }
    
    .tags {
      display: flex;
      gap: 8px;
    }
    
    .tag {
      color: #718096;
      font-size: 14px;
    }
    
    .loading,
    .no-results {
      text-align: center;
      padding: 48px;
      color: #718096;
      font-size: 18px;
    }
    

    Meilleures pratiques 2025

  • Toujours TypeScript : Typage = moins de bugs
  • Composants fonctionnels : Plus de class components
  • Props destructuring : Code plus lisible
  • Nommage explicite : handleClick, isLoading, hasError
  • Un composant = une responsabilité
  • Tests systématiques : Testing Library pour interactions utilisateur
  • Conclusion

    Vous maîtrisez maintenant les trois piliers de React :

  • Composants : Briques réutilisables
  • Props : Communication parent → enfant
  • State : Données dynamiques locales
  • Prochaines étapes

  • Pratiquez avec des mini-projets (todo, calculatrice, météo)
  • Explorez useEffect pour les side effects
  • Lisez l’article suivant : « NPM et Node.js : Gérer les dépendances »
  • Ressources

  • React Documentation – Nouvelle doc officielle
  • TypeScript React Cheatsheet
  • React Testing Library
  • Une remarque, un retour ?

    Cet article est vivant — corrections, contre-arguments et retours de production sont les bienvenus. Trois canaux, choisissez celui qui vous convient.