Création de hooks personnalisés avec React et TypeScript

Inrtoduits avec React 16.8, les Hooks permettent d'utiliser l'état et d'autres fonctionnalités de React au sein de composants fonctionnels, sans avoir à écrire de classes. Bien que React fournisse des hooks intégrés comme useState pour la gestion de l'état, useEffect pour les effets de bord, useContext pour le contexte global, ou encore useRef pour les références, il est possible de créer ses propres hooks.

Un hook personnalisé n'est autre qu'une fonction JavaScript dont le nom commence par le préfixe use. Il permet d'extraire une logique réutilisable. Voici quelques exemples pratiques implémentés avec React et TypeScript.

Suivi des dimensions de la fenêtre

Cet exemple montre comment créer un hook pour écouter les redimensionnements de la fenêtre et récupérer sa largeur et sa hauteur en temps réel.

import { useState, useEffect } from 'react';

type Viewport = {
  w: number;
  h: number;
};

function useViewportDimensions(): Viewport {
  const getDimensions = (): Viewport => ({
    w: window.innerWidth,
    h: window.innerHeight,
  });

  const [dimensions, setDimensions] = useState<Viewport>(getDimensions);

  useEffect(() => {
    const handleResize = () => setDimensions(getDimensions());
    
    window.addEventListener('resize', handleResize);
    return () => window.removeEventListener('resize', handleResize);
  }, []);

  return dimensions;
}

export default useViewportDimensions;

Utilisation dans un composant :

import useViewportDimensions from '../hooks/useViewportDimensions';

function App() {
  const { w, h } = useViewportDimensions();

  return (
    <div>
      <p>Largeur de la fenêtre : {w}</p>
      <p>Hauteur de la fenêtre : {h}</p>
    </div>
  );
}

export default App;

Suivi de la position du défilement

Pour récupérer la position de défilement (scroll) de la page, nous pouvons mettre en place un hook écoutant l'événement scroll.

import { useState, useEffect } from 'react';

interface ScrollCoordinates {
  posX: number;
  posY: number;
}

const useScrollPosition = (): ScrollCoordinates => {
  const [scrollPos, setScrollPos] = useState<ScrollCoordinates>({
    posX: window.scrollX,
    posY: window.scrollY,
  });

  useEffect(() => {
    const updatePosition = () => {
      setScrollPos({
        posX: window.scrollX,
        posY: window.scrollY,
      });
    };

    window.addEventListener('scroll', updatePosition, { passive: true });
    return () => window.removeEventListener('scroll', updatePosition);
  }, []);

  return scrollPos;
};

export default useScrollPosition;

Utilisation dans un composant :

import useScrollPosition from '../hooks/useScrollPosition';

function App() {
  const { posX, posY } = useScrollPosition();

  return (
    <div style={{ height: '200vh', width: '200vw' }}>
      <p>Position X : {posX}</p>
      <p>Position Y : {posY}</p>
    </div>
  );
}

export default App;

Synchronisation avec le stockage local

Ce hook permet de lier un état React à localStorage. Toute mise à jour de l'état sera automatiquement persistée, et l'état initial sera chargé depuis le stockage local s'il existe. L'implémentation utilise un type générique pour s'adapter à tout type de données.

import { useState, useEffect } from 'react';

function usePersistentStorage<T>(storageKey: string, initialData: T) {
  const [state, setState] = useState<T>(() => {
    const storedItem = localStorage.getItem(storageKey);
    return storedItem ? JSON.parse(storedItem) : initialData;
  });

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

  return [state, setState] as const;
}

export default usePersistentStorage;

Utilisation dans un composant :

import usePersistentStorage from '../hooks/usePersistentStorage';

function App() {
  const [framework, setFramework] = usePersistentStorage<string>('app_framework', 'React');

  return (
    <div>
      <button onClick={() => setFramework('Vue')}>
        Changer de framework
      </button>
      <p>Framework actuel : {framework}</p>
    </div>
  );
}

export default App;

Étiquettes: React Hooks TypeScript Custom Hooks localStorage

Publié le 11 juillet à 06h01