🚀 Créer un projet React + TypeScript

# Vite (recommandé)
npm create vite@latest mon-app -- --template react-ts
cd mon-app && npm install

# Create React App (legacy)
npx create-react-app mon-app --template typescript
Vite + React + TS = le setup le plus rapide en 2024. Les fichiers auront l'extension .tsx pour les composants avec JSX.

🧩 Typer les Props

// Avec interface (recommandé)
interface ButtonProps {
  label:     string;
  onClick:   () => void;
  variant?:  "primary" | "secondary" | "danger";
  disabled?: boolean;
  children?: React.ReactNode;
}

const Button = ({ label, onClick, variant = "primary", disabled }: ButtonProps) => {
  return (
    <button
      className={`btn btn-${variant}`}
      onClick={onClick}
      disabled={disabled}
    >
      {label}
    </button>
  );
};

// Avec React.FC (optionnel — moins préféré aujourd'hui)
const Button: React.FC<ButtonProps> = ({ label, onClick }) => (
  <button onClick={onClick}>{label}</button>
);

🪝 Hooks typés

import { useState, useRef, useEffect } from "react";

// useState<T>
const [count, setCount] = useState<number>(0);
const [user, setUser]   = useState<User | null>(null);

// useRef<T> — référence DOM
const inputRef = useRef<HTMLInputElement>(null);
// inputRef.current peut être null → vérifier avant usage
inputRef.current?.focus();

// useRef pour valeur mutable (pas DOM)
const timerRef = useRef<ReturnType<typeof setTimeout>>();

// useEffect
useEffect(() => {
  const controller = new AbortController();
  fetchUser(userId, controller.signal).then(setUser);
  return () => controller.abort();
}, [userId]);

🎯 Événements typés

// Événements formulaires
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
  setValue(e.target.value);
};

const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
  e.preventDefault();
  // traitement
};

const handleClick = (e: React.MouseEvent<HTMLButtonElement>) => {
  console.log(e.currentTarget.id);
};

// Générique pour DRY
type InputHandler = React.ChangeEventHandler<HTMLInputElement>;
const handleInput: InputHandler = (e) => setValue(e.target.value);

🔗 Context API typé

import { createContext, useContext, useState } from "react";

interface AuthContext {
  user:   User | null;
  login:  (credentials: Credentials) => Promise<void>;
  logout: () => void;
}

const AuthContext = createContext<AuthContext | null>(null);

export function useAuth(): AuthContext {
  const ctx = useContext(AuthContext);
  if (!ctx) throw new Error("useAuth must be used within AuthProvider");
  return ctx;
}

export function AuthProvider({ children }: { children: React.ReactNode }) {
  const [user, setUser] = useState<User | null>(null);
  const login = async (creds: Credentials) => { /* ... */ };
  const logout = () => setUser(null);
  return <AuthContext.Provider value={{ user, login, logout }}>{children}</AuthContext.Provider>;
}

🪝 Custom Hook typé

// useFetch — hook générique pour les requêtes
interface FetchState<T> {
  data:    T | null;
  loading: boolean;
  error:   Error | null;
}

function useFetch<T>(url: string): FetchState<T> {
  const [state, setState] = useState<FetchState<T>>({
    data: null, loading: true, error: null
  });

  useEffect(() => {
    fetch(url)
      .then(r => r.json())
      .then((data: T) => setState({ data, loading: false, error: null }))
      .catch((error: Error) => setState({ data: null, loading: false, error }));
  }, [url]);

  return state;
}

// Utilisation
const { data, loading, error } = useFetch<User[]>("/api/users");
📝 Exercices T07 🎯 QCM T07 ▶ Mini-projet T08 — TS + Node →