Contactez-nous

Typer les hooks personnalisés

Apprenez à appliquer le typage statique de TypeScript aux hooks personnalisés React, en typant leurs arguments et leurs valeurs de retour pour une logique réutilisable et sûre.

Sécuriser la logique réutilisable

Les hooks personnalisés sont un outil fondamental dans React pour extraire et partager de la logique stateful ou des effets de bord entre différents composants. Ils encapsulent un comportement spécifique (comme interroger une API, gérer un état complexe, s'abonner à des événements, etc.) et le rendent réutilisable de manière simple et élégante.

Tout comme pour les composants standards, il est crucial de typer correctement vos hooks personnalisés avec TypeScript. Cela implique de définir clairement les types des arguments que votre hook peut accepter et le type de la valeur (ou de l'objet/tableau de valeurs) qu'il retourne. Le typage des hooks personnalisés garantit que la logique qu'ils encapsulent est utilisée correctement par les composants consommateurs et que les données qu'ils fournissent sont comprises et manipulées en toute sécurité.

Typer les arguments du Hook

Si votre hook personnalisé accepte des arguments (comme une valeur initiale, une URL, des options de configuration), vous devez typer ces paramètres comme vous le feriez pour n'importe quelle fonction TypeScript.

import { useState } from 'react';

// Hook simple acceptant une valeur initiale de type number
function useCounter(initialValue: number = 0) {
  const [count, setCount] = useState(initialValue);

  const increment = () => setCount(c => c + 1);
  const decrement = () => setCount(c => c - 1);

  return { count, increment, decrement };
}

// Utilisation:
// const { count, increment } = useCounter(10); // OK
// const { count, increment } = useCounter(); // OK, utilise la valeur par défaut 0
// const { count, increment } = useCounter("zéro"); // Erreur TypeScript: "zéro" n'est pas un nombre

Pour des arguments plus complexes (objets de configuration), utilisez des `interface` ou `type` alias pour définir leur forme :

import { useState, useEffect } from 'react';

interface FetchOptions {
  method?: 'GET' | 'POST'; // Exemple d'options
  headers?: Record;
}

function useFetch(url: string, options?: FetchOptions) {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);

  useEffect(() => {
    // ... logique de fetch utilisant url et options ...
    fetch(url, options)
      .then(res => {
         if (!res.ok) throw new Error('Fetch error');
         return res.json();
      })
      .then((fetchedData: T) => setData(fetchedData))
      .catch(err => setError(err))
      .finally(() => setLoading(false));

  }, [url, options]); // Assurez-vous que les dépendances sont correctes et stables

  return { data, loading, error };
}

// Utilisation:
// const { data } = useFetch('/api/mydata');
// const { data: postData } = useFetch('/api/posts', { method: 'POST' });

Notez l'utilisation de génériques (`useFetch`) dans cet exemple `useFetch`. Cela permet au consommateur du hook de spécifier le type (`T`) des données attendues (`data`), rendant le hook encore plus flexible et sûr.

Typer la valeur de retour du Hook

La valeur retournée par un hook personnalisé est souvent un tableau (comme `useState`) ou un objet contenant l'état et/ou les fonctions manipulatoires. TypeScript peut souvent inférer le type de retour, mais il est souvent préférable de le définir explicitement pour plus de clarté et pour garantir le contrat du hook.

On peut définir le type de retour directement dans la signature de la fonction du hook.

Retour d'un objet

import { useState } from 'react';

// 1. Définir le type de la valeur de retour
interface CounterState {
  count: number;
  increment: () => void;
  decrement: () => void;
}

// 2. Utiliser le type de retour dans la signature du hook
function useCounter(initialValue: number = 0): CounterState {
  const [count, setCount] = useState(initialValue);

  const increment = () => setCount(c => c + 1);
  const decrement = () => setCount(c => c - 1);

  // TypeScript vérifie que l'objet retourné correspond à CounterState
  return { count, increment, decrement }; 
}

// Utilisation:
// const counter = useCounter(5);
// console.log(counter.count); // OK
// counter.increment(); // OK
// counter.reset(); // Erreur TypeScript: 'reset' n'existe pas sur le type CounterState

Retour d'un tableau (Tuple Type)

Si votre hook retourne un tableau où la position et le type de chaque élément sont fixes (comme `useState`), vous pouvez utiliser un type tuple pour le retour.

import { useState, useCallback } from 'react';

// 1. Définir le type tuple pour la valeur de retour
type ToggleResult = [boolean, () => void];

// 2. Utiliser le type tuple comme type de retour
function useToggle(initialState: boolean = false): ToggleResult {
  const [isOn, setIsOn] = useState(initialState);

  const toggle = useCallback(() => setIsOn(prev => !prev), []);

  // Retourner le tableau qui correspond au type tuple
  return [isOn, toggle]; 
}

// Utilisation:
// const [isToggled, performToggle] = useToggle();
// console.log(isToggled); // boolean
// performToggle(); // () => void

Définir explicitement le type de retour présente plusieurs avantages :

  • Contrat clair : Les consommateurs savent exactement ce que le hook retourne.
  • Vérification pour l'auteur : TypeScript s'assure que vous retournez bien la structure et les types attendus depuis votre hook.
  • Refactorisation facilitée : Si vous modifiez ce que le hook retourne, TypeScript vous signalera les endroits où le contrat n'est plus respecté.

Conclusion : Des abstractions logiques sûres et claires

Typer vos hooks personnalisés est une étape essentielle pour garantir la qualité et la maintenabilité de la logique réutilisable dans vos applications React. En définissant soigneusement les types des arguments d'entrée et de la valeur de retour (idéalement de manière explicite), vous créez des abstractions robustes, auto-documentées et faciles à consommer en toute sécurité.

L'utilisation de TypeScript avec les hooks personnalisés renforce leur rôle en tant que blocs de construction fiables, permettant de construire des applications complexes de manière plus organisée et moins sujette aux erreurs.