DevToolBoxGRATUIT
Blog

Guide complet des React Hooks

15 minpar DevToolBox

Les React Hooks ont revolutionne la maniere d'ecrire des composants React. Depuis React 16.8, les Hooks permettent d'utiliser le state, le cycle de vie, le contexte et plus dans les composants fonctionnels. Ce guide complet des React Hooks couvre chaque Hook avec des exemples pratiques.

useState : Gerer le state du composant

useState est le Hook le plus fondamental. Il retourne une valeur stateful et une fonction pour la mettre a jour.

import { useState } from 'react';

function Counter() {
  const [count, setCount] = useState(0);

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={() => setCount(count + 1)}>Increment</button>
      <button onClick={() => setCount(prev => prev - 1)}>Decrement</button>
    </div>
  );
}

// Lazy initialization — runs only on first render
const [data, setData] = useState(() => {
  return JSON.parse(localStorage.getItem('data') || '{}');
});

// Updating objects — always create a new object
const [user, setUser] = useState({ name: '', age: 0 });
setUser(prev => ({ ...prev, name: 'Alice' }));

// Updating arrays — use spread or filter/map
const [items, setItems] = useState<string[]>([]);
setItems(prev => [...prev, 'new item']);
setItems(prev => prev.filter(item => item !== 'remove me'));

useEffect : Effets de bord et cycle de vie

useEffect permet d'effectuer des effets de bord dans les composants fonctionnels.

import { useEffect, useState } from 'react';

function UserProfile({ userId }: { userId: string }) {
  const [user, setUser] = useState(null);

  // Runs when userId changes (componentDidMount + componentDidUpdate)
  useEffect(() => {
    let cancelled = false;
    fetch(`/api/users/${userId}`)
      .then(res => res.json())
      .then(data => {
        if (!cancelled) setUser(data);
      });

    // Cleanup function (componentWillUnmount)
    return () => { cancelled = true; };
  }, [userId]); // dependency array

  return <div>{user?.name}</div>;
}

// Run once on mount
useEffect(() => {
  console.log('Component mounted');
  return () => console.log('Component unmounted');
}, []); // empty dependency array

// Run on every render (rarely needed)
useEffect(() => {
  console.log('Component rendered');
}); // no dependency array

useContext : Consommer le contexte

useContext permet de s'abonner au contexte React sans imbriquer des composants Consumer.

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

// 1. Create a context with a default value
interface ThemeContextType {
  theme: 'light' | 'dark';
  toggleTheme: () => void;
}
const ThemeContext = createContext<ThemeContextType | null>(null);

// 2. Create a provider component
function ThemeProvider({ children }: { children: React.ReactNode }) {
  const [theme, setTheme] = useState<'light' | 'dark'>('light');
  const toggleTheme = () => setTheme(t => t === 'light' ? 'dark' : 'light');

  return (
    <ThemeContext.Provider value={{ theme, toggleTheme }}>
      {children}
    </ThemeContext.Provider>
  );
}

// 3. Consume context with useContext
function ThemeButton() {
  const ctx = useContext(ThemeContext);
  if (!ctx) throw new Error('Must be inside ThemeProvider');

  return (
    <button onClick={ctx.toggleTheme}>
      Current: {ctx.theme}
    </button>
  );
}

// 4. Custom hook for cleaner usage
function useTheme() {
  const ctx = useContext(ThemeContext);
  if (!ctx) throw new Error('useTheme must be used within ThemeProvider');
  return ctx;
}

useMemo : Memoriser les calculs couteux

useMemo met en cache le resultat d'un calcul couteux entre les re-rendus.

import { useMemo, useState } from 'react';

function ExpensiveList({ items, filter }: { items: Item[]; filter: string }) {
  // Only recalculates when items or filter changes
  const filteredItems = useMemo(() => {
    console.log('Filtering...');
    return items.filter(item =>
      item.name.toLowerCase().includes(filter.toLowerCase())
    );
  }, [items, filter]);

  // Memoize a sorted copy
  const sortedItems = useMemo(() => {
    return [...filteredItems].sort((a, b) => a.name.localeCompare(b.name));
  }, [filteredItems]);

  return (
    <ul>
      {sortedItems.map(item => <li key={item.id}>{item.name}</li>)}
    </ul>
  );
}

// Do NOT overuse useMemo — only for truly expensive computations
// Simple operations do not need memoization
const total = useMemo(() => items.reduce((sum, i) => sum + i.price, 0), [items]);

useCallback : Memoriser les fonctions

useCallback retourne une version memorisee d'une fonction callback.

import { useCallback, useState, memo } from 'react';

// Child component wrapped in memo — only re-renders if props change
const SearchInput = memo(({ onSearch }: { onSearch: (q: string) => void }) => {
  console.log('SearchInput rendered');
  return <input onChange={e => onSearch(e.target.value)} />;
});

function SearchPage() {
  const [query, setQuery] = useState('');
  const [results, setResults] = useState([]);

  // Without useCallback, a new function is created every render
  // causing SearchInput to re-render unnecessarily
  const handleSearch = useCallback((q: string) => {
    setQuery(q);
    fetch(`/api/search?q=${q}`)
      .then(res => res.json())
      .then(setResults);
  }, []); // stable reference

  return (
    <div>
      <SearchInput onSearch={handleSearch} />
      <ul>{results.map(r => <li key={r.id}>{r.title}</li>)}</ul>
    </div>
  );
}

useRef : References mutables

useRef retourne un objet ref mutable dont la propriete .current persiste entre les rendus.

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

function TextInputWithFocus() {
  // DOM reference
  const inputRef = useRef<HTMLInputElement>(null);

  useEffect(() => {
    inputRef.current?.focus(); // auto-focus on mount
  }, []);

  return <input ref={inputRef} placeholder="Auto-focused" />;
}

function StopWatch() {
  const [time, setTime] = useState(0);
  const intervalRef = useRef<NodeJS.Timeout | null>(null);

  const start = () => {
    intervalRef.current = setInterval(() => {
      setTime(t => t + 1);
    }, 1000);
  };

  const stop = () => {
    if (intervalRef.current) clearInterval(intervalRef.current);
  };

  // Track previous value
  const prevTimeRef = useRef(time);
  useEffect(() => { prevTimeRef.current = time; });

  return (
    <div>
      <p>Time: {time}s (prev: {prevTimeRef.current}s)</p>
      <button onClick={start}>Start</button>
      <button onClick={stop}>Stop</button>
    </div>
  );
}

useReducer : Logique d'etat complexe

useReducer est une alternative a useState pour la logique d'etat complexe.

import { useReducer } from 'react';

interface State {
  count: number;
  step: number;
}

type Action =
  | { type: 'increment' }
  | { type: 'decrement' }
  | { type: 'reset' }
  | { type: 'setStep'; payload: number };

function reducer(state: State, action: Action): State {
  switch (action.type) {
    case 'increment':
      return { ...state, count: state.count + state.step };
    case 'decrement':
      return { ...state, count: state.count - state.step };
    case 'reset':
      return { count: 0, step: 1 };
    case 'setStep':
      return { ...state, step: action.payload };
    default:
      return state;
  }
}

function Counter() {
  const [state, dispatch] = useReducer(reducer, { count: 0, step: 1 });

  return (
    <div>
      <p>Count: {state.count}</p>
      <input
        type="number"
        value={state.step}
        onChange={e => dispatch({ type: 'setStep', payload: Number(e.target.value) })}
      />
      <button onClick={() => dispatch({ type: 'increment' })}>+</button>
      <button onClick={() => dispatch({ type: 'decrement' })}>-</button>
      <button onClick={() => dispatch({ type: 'reset' })}>Reset</button>
    </div>
  );
}

Hooks personnalises : Logique reutilisable

Les Hooks personnalises permettent d'extraire la logique des composants en fonctions reutilisables.

// useLocalStorage — persist state to localStorage
function useLocalStorage<T>(key: string, initialValue: T) {
  const [value, setValue] = useState<T>(() => {
    try {
      const item = localStorage.getItem(key);
      return item ? JSON.parse(item) : initialValue;
    } catch {
      return initialValue;
    }
  });

  useEffect(() => {
    localStorage.setItem(key, JSON.stringify(value));
  }, [key, value]);

  return [value, setValue] as const;
}

// useDebounce — debounce a rapidly changing value
function useDebounce<T>(value: T, delay: number): T {
  const [debounced, setDebounced] = useState(value);
  useEffect(() => {
    const timer = setTimeout(() => setDebounced(value), delay);
    return () => clearTimeout(timer);
  }, [value, delay]);
  return debounced;
}

// useFetch — generic data fetching
function useFetch<T>(url: string) {
  const [data, setData] = useState<T | null>(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState<Error | null>(null);

  useEffect(() => {
    let cancelled = false;
    setLoading(true);
    fetch(url)
      .then(res => res.json())
      .then(data => { if (!cancelled) { setData(data); setLoading(false); } })
      .catch(err => { if (!cancelled) { setError(err); setLoading(false); } });
    return () => { cancelled = true; };
  }, [url]);

  return { data, loading, error };
}

// Usage
function App() {
  const [name, setName] = useLocalStorage('name', '');
  const debouncedName = useDebounce(name, 300);
  const { data, loading } = useFetch<User[]>(`/api/search?q=${debouncedName}`);
}

Regles des Hooks

Deux regles essentielles a suivre avec les Hooks.

  • Appelez les Hooks uniquement au niveau superieur. Pas dans les boucles, conditions ou fonctions imbriquees.
  • Appelez les Hooks uniquement depuis des fonctions React.
// WRONG — Hook inside a condition
function Bad({ isLoggedIn }) {
  if (isLoggedIn) {
    const [user, setUser] = useState(null); // breaks Hook order
  }
}

// CORRECT — condition inside the Hook
function Good({ isLoggedIn }) {
  const [user, setUser] = useState(null);
  useEffect(() => {
    if (isLoggedIn) fetchUser().then(setUser);
  }, [isLoggedIn]);
}

Pieges courants et solutions

Closures obsoletes dans useEffect

Quand vous referencez un state dans useEffect sans l'inclure dans les dependances, vous obtenez une closure obsolete.

// BUG: stale closure
function Timer() {
  const [count, setCount] = useState(0);
  useEffect(() => {
    const id = setInterval(() => {
      console.log(count); // always logs 0 (stale!)
      setCount(count + 1); // always sets to 1
    }, 1000);
    return () => clearInterval(id);
  }, []); // count is missing from dependencies
}

// FIX: use functional update
useEffect(() => {
  const id = setInterval(() => {
    setCount(prev => prev + 1); // always uses latest value
  }, 1000);
  return () => clearInterval(id);
}, []); // safe with functional update

Boucles infinies avec useEffect

Modifier le state dans useEffect sans dependances correctes cause des boucles de re-rendu infinies.

// BUG: infinite loop
useEffect(() => {
  setCount(count + 1); // triggers re-render, which runs effect again
}); // no dependency array = runs every render

// FIX: add dependency array
useEffect(() => {
  if (count < 10) setCount(count + 1);
}, [count]); // only runs when count changes

Dependances objet/tableau

Les objets et tableaux sont compares par reference, pas par valeur.

// BUG: new object every render
function App() {
  const options = { page: 1, limit: 10 }; // new ref each render
  useEffect(() => {
    fetchData(options);
  }, [options]); // runs every render!
}

// FIX: useMemo to stabilize the reference
function App() {
  const options = useMemo(() => ({ page: 1, limit: 10 }), []);
  useEffect(() => {
    fetchData(options);
  }, [options]); // stable reference
}

Questions frequentes

Que sont les React Hooks ?

Les React Hooks sont des fonctions qui permettent d'utiliser les fonctionnalites React dans les composants fonctionnels.

Quelle est la difference entre useMemo et useCallback ?

useMemo memorise une valeur calculee, useCallback memorise une reference de fonction.

Quand utiliser useReducer au lieu de useState ?

Quand la logique d'etat est complexe ou implique plusieurs sous-valeurs.

Peut-on utiliser les Hooks dans les composants de classe ?

Non, les Hooks fonctionnent uniquement dans les composants fonctionnels.

Comment eviter les boucles infinies avec useEffect ?

Specifiez toujours le bon tableau de dependances.

Les React Hooks sont essentiels pour le developpement React moderne. Maitrisez ces Hooks principaux pour construire des interfaces efficacement.

𝕏 Twitterin LinkedIn
Cet article vous a-t-il aidé ?

Restez informé

Recevez des astuces dev et les nouveaux outils chaque semaine.

Pas de spam. Désabonnez-vous à tout moment.

Essayez ces outils associés

{ }JSON FormatterJSTypeScript to JavaScript&;HTML Entity Encoder / Decoder

Articles connexes

React Server Components : Guide Complet 2026

Maîtrisez les React Server Components : architecture, récupération de données et streaming.

Methodes de tableau JavaScript : aide-memoire complet avec exemples

Reference complete des methodes de tableau JavaScript. map, filter, reduce, find, sort, flat, flatMap, splice et methodes ES2023.

TypeScript Type Guards : Guide Complet de Vérification de Type

Maîtrisez les type guards TypeScript : typeof, instanceof, in et guards personnalisés.