Frontend

Come usare i custom hook in React

Autore

Manuel Ricci

I Custom Hook in React consentono di estrarre la logica dei componenti in funzioni riutilizzabili. Quando si crea un Custom Hook, si sta essenzialmente creando una funzione che può utilizzare altri hook (come useState, useEffect, useContext, ecc.) per fornire una logica condivisa tra più componenti React. I Custom Hook offrono un modo potente per condividere la logica tra componenti senza ricorrere a HOC (Higher-Order Components) o render props.

Cosa sono HOC e render props?

Gli High Order Components (HOC) e le Render Props sono due concetti avanzati in React che aiutano a gestire la logica riutilizzabile tra componenti.

High Order Components

Un High Order Component è una funzione che prende un componente e restituisce un nuovo componente. L'HOC avvolge il componente originale, fornendo logica o dati aggiuntivi senza modificare il comportamento del componente stesso. Questo pattern è utile per la condivisione di logica tra componenti diversi, come la gestione dello stato, l'accesso ai dati, la manipolazione di props, ecc.

Esempio di HOC in JavaScript:

1function withUserData(WrappedComponent) {
2  return class extends React.Component {
3    constructor(props) {
4      super(props);
5      this.state = {
6        user: null,
7      };
8    }
9
10    componentDidMount() {
11      // Simulazione di richiesta dati utente
12      this.setState({ user: { name: 'Mario Rossi', age: 30 } });
13    }
14
15    render() {
16      return <WrappedComponent {...this.props} user={this.state.user} />;
17    }
18  };
19}

Questo HOC withUserData aggiunge dati dell'utente al componente che avvolge, consentendo a quel componente di accedere e visualizzare i dati dell'utente senza preoccuparsi di come quei dati vengono ottenuti o gestiti.

Render props

La Render Prop è una tecnica in React per condividere codice tra componenti utilizzando una prop con una funzione che restituisce un elemento React. Questo pattern consente di creare componenti più flessibili e riutilizzabili, offrendo un modo per condividere logica dinamica e stato tra componenti senza dover ricorrere all'ereditarietà.

Esempio di utilizzo delle Render Props in JavaScript:

1class UserData extends React.Component {
2  constructor(props) {
3    super(props);
4    this.state = {
5      user: { name: 'Mario Rossi', age: 30 },
6    };
7  }
8
9  render() {
10    return this.props.render(this.state.user);
11  }
12}
13
14// Utilizzo
15<UserData render={user => (
16  <div>
17    Nome: {user.name}, Età: {user.age}
18  </div>
19)} />

In questo esempio, il componente UserData riceve una prop render, che è una funzione. Questa funzione viene chiamata con i dati dell'utente come argomento, permettendo al componente che utilizza UserData di decidere come renderizzare quei dati.

Entrambi i concetti, HOC e Render Props, offrono modi potenti e flessibili per costruire componenti riutilizzabili in React, facilitando la gestione della logica condivisa in un'applicazione. La scelta tra l'uno e l'altro dipende dalle preferenze personali e dai requisiti specifici del progetto.

HOC e render props sono solo per i componenti di classe?

Sia gli High Order Components (HOC) sia le Render Props possono essere utilizzati sia con i componenti di classe che con quelli funzionali. Tuttavia, la loro popolarità è iniziata in un contesto in cui i componenti di classe erano più comuni, soprattutto per gli HOC, che si adattano naturalmente al pattern di estensione dei componenti.

Con l'introduzione e l'adozione diffusa degli Hooks, l'attenzione si è spostata verso i componenti funzionali per la gestione dello stato e degli effetti collaterali, rendendo alcune delle applicazioni degli HOC e delle Render Props meno comuni o necessarie.

HOC con Componenti Funzionali

Gli HOC possono avvolgere sia componenti di classe che componenti funzionali, fornendo loro props aggiuntive o logica. Quando si avvolge un componente funzionale con un HOC, il componente riceve props o logica aggiuntiva dall'HOC esattamente come farebbe un componente di classe.

Render Props con Componenti Funzionali

Le Render Props possono essere utilizzate in componenti funzionali per condividere logica in modo simile. Con gli Hooks, tuttavia, molti dei casi d'uso delle Render Props possono essere gestiti in modo più diretto e conciso, ad esempio utilizzando l'hook useState o useEffect per gestire lo stato o gli effetti collaterali.

Esempio con Hook

Ad esempio, la logica di condivisione dello stato dell'utente potrebbe essere implementata con un hook personalizzato:

1function useUserData() {
2  const [user, setUser] = useState(null);
3
4  useEffect(() => {
5    // Simula il caricamento dei dati dell'utente
6    setUser({ name: 'Mario Rossi', age: 30 });
7  }, []);
8
9  return user;
10}
11
12function MyComponent() {
13  const user = useUserData();
14
15  if (!user) return <div>Caricamento...</div>;
16  return <div>Nome: {user.name}, Età: {user.age}</div>;
17}

Questo approccio con gli Hooks rende il codice più semplice e diretto per i componenti funzionali, riducendo la necessità di utilizzare HOC o Render Props per molti casi d'uso. Tuttavia, HOC e Render Props rimangono strumenti validi e potenti nella cassetta degli attrezzi di React, soprattutto per determinati pattern di progettazione o quando si lavora in progetti che adottano stili architetturali specifici.

Tornando hai custom hook…

Consideriamo un semplice Custom Hook denominato useCounter che permette di incrementare, decrementare, e resettare un contatore. Useremo TypeScript per assicurare la tipizzazione forte dei parametri e dei valori restituiti.

1import { useState } from 'react';
2
3// Definizione del Custom Hook con TypeScript
4function useCounter(initialValue: number = 0) {
5  const [count, setCount] = useState<number>(initialValue);
6
7  // Incrementa il contatore
8  const increment = () => setCount(count + 1);
9
10  // Decrementa il contatore
11  const decrement = () => setCount(count - 1);
12
13  // Resetta il contatore al valore iniziale
14  const reset = () => setCount(initialValue);
15
16  // Restituisce il valore del contatore e le funzioni per modificarlo
17  return { count, increment, decrement, reset };
18}
19
20export default useCounter;

Questo hook può essere utilizzato in qualsiasi componente funzionale per fornire la sua logica di conteggio senza dover riscrivere lo stesso codice.

Utilizzo di useCounter

Ecco come potresti utilizzare useCounter in un componente:

1import React from 'react';
2import useCounter from './useCounter'; // Assumi che useCounter sia nel percorso corretto
3
4const CounterComponent: React.FC = () => {
5  const { count, increment, decrement, reset } = useCounter(0);
6
7  return (
8    <div>
9      <p>Conteggio: {count}</p>
10      <button onClick={increment}>Incrementa</button>
11      <button onClick={decrement}>Decrementa</button>
12      <button onClick={reset}>Reset</button>
13    </div>
14  );
15}
16
17export default CounterComponent;

Vantaggi dei Custom Hook

  1. Riutilizzabilità della Logica: I Custom Hook permettono di estrarre e condividere logica tra componenti, migliorando la riutilizzabilità del codice.
  2. Separazione delle Preoccupazioni: Consentono di separare la logica di business dalla UI, mantenendo i componenti puliti e focalizzati sulla presentazione.
  3. Componibilità: I Custom Hook possono utilizzare altri hook al loro interno, rendendoli estremamente componibili e modulari.
  4. Semplificazione del Codice: Aiutano a evitare l'uso eccessivo di HOC o render props, che possono complicare l'albero dei componenti e la tracciabilità dei dati.

Un esempio più concreto: il debounce

Il debounce è una tecnica di programmazione utilizzata per limitare la frequenza con cui una funzione può essere eseguita. Questo è particolarmente utile nelle operazioni di ricerca per ridurre il numero di chiamate effettuate per esempio ad un'API durante la digitazione dell'utente. Implementiamo un Custom Hook denominato useDebounce che ritarda l'esecuzione di una funzione fino a quando non passa un certo intervallo di tempo da quando l'ultimo evento è stato inviato.

useDebounce Custom Hook

Questo hook accetterà un valore (la query di ricerca, in questo caso) e un ritardo in millisecondi, e restituirà un valore debounced che cambia solo dopo che l'intervallo di tempo specificato è passato senza altri cambiamenti al valore.

1import { useState, useEffect } from 'react';
2
3// Custom Hook per il debounce di un valore
4function useDebounce<T>(value: T, delay: number): T {
5  // Stato interno per tenere traccia del valore debounced
6  const [debouncedValue, setDebouncedValue] = useState<T>(value);
7
8  useEffect(() => {
9    // Imposta un timeout per aggiornare il valore debounced dopo il ritardo
10    const handler = setTimeout(() => {
11      setDebouncedValue(value);
12    }, delay);
13
14    // Cancella il timeout se il valore cambia (anche se il componente viene smontato)
15    return () => {
16      clearTimeout(handler);
17    };
18  }, [value, delay]); // Solo se `value` o `delay` cambiano, ricalcola il debounced value
19
20  return debouncedValue;
21}
22
23export default useDebounce;

Utilizzo di useDebounce per la Ricerca

Supponiamo di avere un componente di ricerca che accetta l'input dell'utente e utilizza useDebounce per posticipare la ricerca fino a quando l'utente ha smesso di digitare per un intervallo di tempo specifico.

1import React, { useState } from 'react';
2import useDebounce from './useDebounce'; // Assumi che useDebounce sia nel percorso corretto
3
4const SearchComponent: React.FC = () => {
5  const [query, setQuery] = useState('');
6  const debouncedQuery = useDebounce<string>(query, 500); // Debounce di 500ms
7
8  // Effettua la ricerca quando `debouncedQuery` cambia
9  useEffect(() => {
10    if (debouncedQuery) {
11      console.log(`Effettua la ricerca per: ${debouncedQuery}`);
12      // Qui potresti invocare un'API di ricerca o filtrare i dati localmente
13    }
14  }, [debouncedQuery]);
15
16  return (
17    <div>
18      <input
19        type="text"
20        value={query}
21        onChange={(e) => setQuery(e.target.value)}
22        placeholder="Cerca..."
23      />
24    </div>
25  );
26}
27
28export default SearchComponent;

In questo esempio, useDebounce viene utilizzato per ritardare la ricerca fino a quando l'utente non ha smesso di digitare per 500 millisecondi. Questo riduce significativamente il numero di chiamate che potrebbero essere necessarie se si eseguisse una ricerca ogni volta che l'utente digita un carattere, migliorando le prestazioni e l'esperienza dell'utente, specialmente in situazioni con connessioni lente o limitazioni API.

Conclusioni

I Custom Hook in React offrono un meccanismo elegante e potente per la condivisione della logica di stato e di effetti tra i componenti. Utilizzando TypeScript, si guadagna in chiarezza e sicurezza del tipo, assicurando che i dati utilizzati all'interno dei hook siano correttamente tipizzati. Attraverso esempi come useCounter, si può vedere come i Custom Hook possano semplificare significativamente il codice dei componenti, rendendo la logica facilmente riutilizzabile e mantenibile.

Caricamento...

Diventiamo amici di penna? ✒️

Iscriviti alla newsletter per ricevere una mail ogni paio di settimane con le ultime novità, gli ultimi approfondimenti e gli ultimi corsi gratuiti puubblicati. Ogni tanto potrei scrivere qualcosa di commerciale, ma solo se mi autorizzi, altrimenti non ti disturberò oltre.

Se non ti piace la newsletter ti ricordo la community su Discord, dove puoi chiedere aiuto, fare domande e condividere le tue esperienze (ma soprattutto scambiare due meme con me). Ti aspetto!

Ho in previsione di mandarti una newsletter ogni due settimane e una commerciale quando capita.