Frontend

Hook in React: cosa sono e come usarli

Autore

Manuel Ricci

Gli hook in React sono funzioni che ti permettono di "agganciarti" alle funzionalità di React, come lo stato e il ciclo di vita dei componenti, da componenti funzionali. Prima dell'introduzione degli hook con React 16.8, queste funzionalità erano disponibili solo nei componenti di classe. Gli hook più comunemente usati sono useState, useEffect, useContext, useReducer, e useRef.

useState

L'hook useState è uno degli hook fondamentali in React per gestire lo stato locale di un componente funzionale. Prima dell'introduzione degli hook, lo stato locale poteva essere usato solo nei componenti di classe. useState rende possibile utilizzare lo stato anche nei componenti funzionali, rendendoli più potenti e flessibili.

Sintassi Base di useState

La sintassi base di useState è piuttosto semplice:

1const [state, setState] = useState(initialState);
  • initialState: il valore iniziale dello stato. Può essere qualsiasi tipo di dato: numero, stringa, array, oggetto, ecc.
  • state: la variabile corrente dello stato, inizializzata con initialState.
  • setState: la funzione che viene utilizzata per aggiornare state. Quando setState viene chiamata, React ri-renderizza il componente con il nuovo stato.

Esempio di utilizzo di useState

Ecco un esempio dettagliato di come utilizzare useState in un componente funzionale React con TypeScript:

1import React, { useState } from 'react';
2
3const Counter: React.FC = () => {
4  // Inizializza lo stato 'count' con valore 0
5  const [count, setCount] = useState<number>(0);
6
7  const handleIncrement = () => {
8    // Aggiorna lo stato 'count', incrementandolo di 1
9    setCount(prevCount => prevCount + 1);
10  };
11
12  const handleDecrement = () => {
13    // Aggiorna lo stato 'count', decrementandolo di 1
14    setCount(prevCount => prevCount - 1);
15  };
16
17  return (
18    <div>
19      <p>Conto attuale: {count}</p>
20      <button onClick={handleIncrement}>Incrementa</button>
21      <button onClick={handleDecrement}>Decrementa</button>
22    </div>
23  );
24};

Punti Chiave di useState

  • Immutabilità dello Stato: È importante ricordare che lo stato in React è immutabile. Questo significa che non dovresti mai modificare direttamente lo stato (ad esempio, usando state++). Invece, dovresti sempre utilizzare la funzione setState per aggiornare lo stato, che crea una nuova istanza dello stato anziché modificarlo direttamente.
  • Funzioni di Aggiornamento Lazy: Quando il nuovo stato dipende dallo stato precedente, è consigliato usare una funzione di aggiornamento che riceve lo stato precedente come argomento, garantendo che l'aggiornamento sia basato sul valore attuale dello stato.
  • Valori Iniziali Calcolati: Se il valore iniziale dello stato ha bisogno di qualche calcolo, è possibile passare una funzione a useState. Questa funzione verrà eseguita solo al momento della creazione del componente, migliorando le performance se il calcolo iniziale è costoso.
1const [state, setState] = useState(() => {
2  // Calcola il valore iniziale complesso qui
3  const initialState = someExpensiveComputation(props);
4  return initialState;
5});

useState in breve

useState è uno strumento potente che introduce la gestione dello stato nei componenti funzionali di React, consentendo di costruire applicazioni interattive e reattive con meno boilerplate rispetto ai componenti di classe. La sua semplicità d'uso, unita alla potenza di React, rende la creazione di interfacce utente dinamiche e interattive molto più accessibile e gestibile.

useEffect

L'hook useEffect è un altro strumento fondamentale nell'arsenale di React che permette di eseguire effetti collaterali in componenti funzionali. Questo hook è il sostituto dei metodi di ciclo di vita componentDidMount, componentDidUpdate, e componentWillUnmount nei componenti di classe, consolidandoli in un'unica API.

Sintassi Base di useEffect

La sintassi base di useEffect è la seguente:

1useEffect(() => {
2  // Codice per l'effetto collaterale
3
4  return () => {
5    // Pulizia (opzionale)
6  };
7}, [dipendenze]);
  • Il primo argomento è una funzione che React chiamerà dopo che il DOM è stato aggiornato. Questa funzione può eseguire qualsiasi effetto collaterale, come operazioni di input/output, sottoscrizioni, o manualmente modificare il DOM.
  • Il secondo argomento è un array di dipendenze che determina quando l'effetto dovrebbe essere ri-eseguito. Se l'array è vuoto ([]), l'effetto viene eseguito solo al montaggio e al smontaggio del componente. Se l'array contiene valori, l'effetto verrà ri-eseguito solo quando i valori specificati cambiano.

Esempio di utilizzo di useEffect

Ecco come useEffect può essere utilizzato in un componente funzionale React con TypeScript:

1import React, { useState, useEffect } from 'react';
2
3const Timer: React.FC = () => {
4  const [seconds, setSeconds] = useState(0);
5
6  useEffect(() => {
7    // Imposta un timer che aggiorna 'seconds' ogni secondo
8    const interval = setInterval(() => {
9      setSeconds(prevSeconds => prevSeconds + 1);
10    }, 1000);
11
12    // Funzione di pulizia che viene chiamata al smontaggio del componente
13    return () => clearInterval(interval);
14  }, []); // L'array vuoto indica che questo effetto non ha dipendenze e viene eseguito solo al montaggio
15
16  return <div>Secondi trascorsi: {seconds}</div>;
17};

Punti Chiave di useEffect

  • Effetti Collaterali: Gli effetti collaterali sono operazioni che possono influenzare componenti esterni al componente che viene renderizzato, come richieste di rete, sottoscrizioni, o manualmente modificare il DOM. useEffect gestisce questi effetti all'interno dei componenti funzionali.
  • Pulizia dell'Effetto: Se l'effetto collaterale necessita di pulizia (ad esempio, cancellare un timer o annullare una sottoscrizione), puoi restituire una funzione di pulizia dall'interno della funzione passata a useEffect. React chiamerà questa funzione di pulizia quando il componente viene smontato o prima di eseguire l'effetto la prossima volta, garantendo che le risorse siano liberate correttamente.
  • Ottimizzazione delle Prestazioni: Utilizzando l'array di dipendenze, puoi controllare quanto frequentemente l'effetto viene eseguito, evitando esecuzioni inutili e migliorando le prestazioni dell'applicazione.

useEffect in breve

useEffect offre un meccanismo potente e flessibile per eseguire effetti collaterali nei componenti funzionali di React. Attraverso la sua API unificata, rende più semplice gestire le operazioni laterali, come sottoscrizioni a eventi esterni o timer, all'interno dei componenti, mantenendo il codice organizzato e performante.

useContext

L'hook useContext di React consente di accedere ai valori di un contesto (context) senza dover utilizzare il componente Consumer all'interno di un componente funzionale. Questo rende molto più semplice e pulito l'accesso ai dati condivisi come temi, preferenze linguistiche, dati utente, ecc., attraverso l'albero dei componenti.

Sintassi Base di useContext

La sintassi base di useContext è la seguente:

1const value = useContext(MyContext);
  • MyContext: il contesto dal quale si desidera leggere il valore corrente. Questo contesto (context) deve essere creato utilizzando React.createContext e può essere distribuito a tutti i componenti dell'applicazione tramite un componente Provider.

Esempio di utilizzo di useContext

Ecco un esempio pratico di come utilizzare useContext in un componente funzionale React con TypeScript:

1import React, { useContext, createContext } from 'react';
2
3// Creazione di un contesto con un valore predefinito
4const ThemeContext = createContext('light');
5
6const ThemedButton: React.FC = () => {
7  // Utilizzo di useContext per accedere al valore corrente del contesto
8  const theme = useContext(ThemeContext);
9
10  return <button className={theme}>Sono un bottone a tema {theme}</button>;
11};
12
13const App: React.FC = () => {
14  return (
15    // Il Provider rende il valore "dark" disponibile a tutti i componenti discendenti
16    <ThemeContext.Provider value="dark">
17      <ThemedButton />
18    </ThemeContext.Provider>
19  );
20};

Se avessimo deciso di non optare per useContext, ecco un esempio di come potresti passare il tema ai componenti discendenti senza utilizzare l'hook, ma tramite il passaggio esplicito delle props. Questo approccio dimostra come si lavorava prima dell'introduzione degli hook o in scenari in cui si preferisce evitare l'uso del contesto per motivi di semplicità o di architettura del progetto.

1import React from 'react';
2
3// Componente Button che riceve il tema come prop
4const ThemedButton: React.FC<{ theme: string }> = ({ theme }) => {
5  return <button className={theme}>Sono un bottone a tema {theme}</button>;
6};
7
8// Componente App che passa il tema esplicitamente a ThemedButton
9const App: React.FC = () => {
10  const theme = 'dark'; // Definizione del tema all'interno del componente App
11  return (
12    <div>
13      {/* Passaggio del tema come prop a ThemedButton */}
14      <ThemedButton theme={theme} />
15    </div>
16  );
17};

Nello specifico cosa è stato fatto:

  • Definizione del Tema: In questo esempio, il tema è definito come una variabile all'interno del componente App. Questo tema è poi passato direttamente al componente ThemedButton come una prop.
  • Passaggio delle Props: Il componente ThemedButton riceve il tema come una prop e lo utilizza per determinare la classe del bottone. Questo metodo richiede che ogni componente che necessita del tema debba riceverlo esplicitamente tramite le props da ogni componente genitore.
  • Manutenibilità: Mentre questo approccio funziona bene per strutture di componenti piccole o per dati che cambiano raramente, può diventare rapidamente oneroso e difficile da mantenere man mano che l'applicazione cresce e il numero di componenti che necessitano del dato condiviso aumenta.

Le differenze principali con l’esempio con useContext:

  • Verbosità: Senza useContext, il dato (in questo caso, il tema) deve essere passato esplicitamente attraverso l'albero dei componenti, da genitore a figlio. Questo può aumentare la verbosità e complicare la manutenzione, specialmente in applicazioni di grandi dimensioni.
  • Efficienza: L'uso di useContext riduce il boilerplate necessario per passare dati attraverso l'albero dei componenti, rendendo il codice più pulito e facile da seguire.
  • Riutilizzabilità: Con useContext, i componenti possono accedere ai dati del contesto direttamente, senza dipendere dalle props passate dai loro genitori, migliorando la riutilizzabilità dei componenti.

L'introduzione di useContext in React ha significativamente semplificato la condivisione dei dati tra componenti, specialmente in applicazioni complesse, rendendo l'architettura dell'applicazione più pulita e meno propensa ad errori di propagazione dei dati.

Punti Chiave di useContext

  • Accesso Semplice ai Dati Condivisi: useContext rende molto più semplice per i componenti discendenti accedere ai dati condivisi senza doverli passare esplicitamente attraverso ogni livello dell'albero dei componenti.
  • Riduce il Boilerplate: A differenza dell'approccio classico che utilizza il componente Consumer per accedere ai dati del contesto, useContext permette di accedere direttamente al valore del contesto con una singola riga di codice, riducendo significativamente il boilerplate.
  • Uso con Provider: Per poter utilizzare useContext, il componente che desidera accedere ai dati del contesto deve trovarsi all'interno dell'albero dei componenti di un Provider per quel contesto specifico. Il Provider permette di sovrascrivere il valore del contesto per tutti i suoi componenti discendenti.
  • Performance: L'uso di useContext non introduce di per sé problemi di performance significativi, ma è importante essere consapevoli che ogni aggiornamento al contesto causerà il re-render dei componenti consumatori. Pertanto, è buona pratica ottimizzare i componenti consumatori per evitare render inutili.

useContext in breve

useContext è uno strumento essenziale per la gestione efficace dei dati condivisi all'interno delle applicazioni React. Facilita notevolmente l'accesso ai valori dei contesti senza la necessità di passaggi di props multi-livello, mantenendo il codice pulito e organizzato. L'uso di useContext in combinazione con un'architettura ben pensata dell'applicazione può significativamente migliorare l'efficienza dello sviluppo e la manutenibilità del codice.

useReducer

L'hook useReducer è un altro strumento essenziale in React per la gestione dello stato, in particolare quando si ha a che fare con logiche di aggiornamento dello stato più complesse. useReducer offre un'alternativa a useState, permettendo di gestire lo stato attraverso reducer - funzioni che determinano come cambia lo stato in risposta ad azioni.

Sintassi Base di useReducer

La sintassi base di useReducer è la seguente:

1const [state, dispatch] = useReducer(reducer, initialState);
  • reducer: Una funzione che riceve lo stato corrente e un'azione, e restituisce un nuovo stato. La forma della funzione reducer è (state, action) => newState.
  • initialState: Il valore iniziale dello stato.
  • state: Lo stato corrente, tenuto traccia da useReducer.
  • dispatch: Una funzione che puoi chiamare con un'azione per richiedere un aggiornamento dello stato. L'azione viene poi gestita dal tuo reducer per determinare il nuovo stato.

Esempio di utilizzo di useReducer

Supponiamo di avere un componente che gestisce un contatore. Utilizzeremo useReducer per gestire lo stato del contatore.

1import React, { useReducer } from 'react';
2
3// Definizione del tipo di azione
4type ActionType = { type: 'increment' } | { type: 'decrement' };
5
6// Funzione reducer che determina come cambia lo stato in base all'azione ricevuta
7function counterReducer(state: number, action: ActionType): number {
8  switch (action.type) {
9    case 'increment':
10      return state + 1;
11    case 'decrement':
12      return state - 1;
13    default:
14      throw new Error();
15  }
16}
17
18const Counter: React.FC = () => {
19  // Uso di useReducer per gestire lo stato del contatore
20  const [count, dispatch] = useReducer(counterReducer, 0); // Inizializzazione a 0
21
22  return (
23    <div>
24      Conto: {count}
25      <button onClick={() => dispatch({ type: 'increment' })}>+</button>
26      <button onClick={() => dispatch({ type: 'decrement' })}>-</button>
27    </div>
28  );
29};

Punti Chiave di useReducer

  • Gestione dello Stato Complesso: useReducer è particolarmente utile quando lo stato logico del componente diventa complesso o quando il prossimo stato dipende dal precedente in modi non banali.
  • Performance: useReducer può anche ottimizzare le prestazioni per componenti che attivano aggiornamenti profondi, poiché è possibile passare dispatch verso il basso invece di callback, evitando così la creazione di nuove funzioni a ogni render.
  • Leggibilità e Manutenibilità: Separare la logica di aggiornamento dello stato dal componente può rendere il codice più leggibile e più facile da mantenere, specialmente per logiche di stato complesse.
  • Uso con Context: useReducer è spesso usato in combinazione con useContext per gestire e distribuire lo stato attraverso componenti profondamente annidati, offrendo un'alternativa più leggera e flessibile alla Redux per la gestione dello stato globale.

useReducer in breve

useReducer fornisce un metodo potente e flessibile per gestire lo stato all'interno dei componenti funzionali React, specialmente quando ci si trova di fronte a logiche di aggiornamento complesse. Attraverso l'uso di reducer per gestire come lo stato cambia in risposta ad azioni, i componenti possono rimanere organizzati, testabili e manutenibili.

useRef

L'hook useRef è uno strumento versatile in React che può essere utilizzato per diverse finalità, tra cui l'accesso diretto agli elementi del DOM e la conservazione di un valore mutabile attraverso i render del componente senza causare un aggiornamento dello stesso. A differenza dello stato gestito tramite useState o useReducer, le modifiche a un ref non causano il re-render del componente.

Sintassi Base di useRef

La sintassi base di useRef è la seguente:

1const refContainer = useRef(initialValue);
  • initialValue: Il valore iniziale del ref. Il valore contenuto nel ref può essere qualsiasi tipo di dato.
  • refContainer: Un oggetto contenente una proprietà .current che viene inizializzata al valore passato ad useRef.

Esempio di utilizzo di useRef

Ecco un esempio pratico che mostra come utilizzare useRef per accedere a un elemento del DOM in un componente funzionale React con TypeScript.

1import React, { useRef, useEffect } from 'react';
2
3const TextInputWithFocus: React.FC = () => {
4  // Crea un ref per l'elemento input del DOM
5  const inputEl = useRef<HTMLInputElement>(null);
6
7  useEffect(() => {
8    // Mette automaticamente il focus sull'input dopo il montaggio del componente
9    inputEl.current?.focus();
10  }, []);
11
12  return <input ref={inputEl} type="text" />;
13};

Punti Chiave di useRef

  • Accesso agli Elementi del DOM: useRef fornisce un modo per accedere direttamente agli elementi del DOM in React. Questo è utile per manipolare l'elemento, ad esempio, per impostare il focus o per leggere il valore.
  • Valori Mutabili che non Causano Re-render: A differenza di useState, l'aggiornamento del valore contenuto in .current di un ref non provoca il re-render del componente. Questo lo rende ideale per tenere traccia di valori che devono persistere tra i render ma non necessitano di causare un aggiornamento dell'interfaccia utente.
  • Persistenza dei Dati tra i Render: I refs possono essere utilizzati per conservare qualsiasi valore mutabile che vuoi persistere esattamente come è tra i render, senza causare l'aggiornamento del componente ogni volta che il valore cambia.

Dimostrare l’assenza di re-rendering in useRef

Per dimostrare che useState causa il re-render di un componente in React mentre useRef no, possiamo creare un semplice esperimento con due componenti: uno che utilizza useState e l'altro che utilizza useRef. In entrambi i componenti, incrementeremo il valore memorizzato (stato o ref) ogni volta che l'utente clicca un bottone. Aggiungeremo anche un messaggio di log nella console per osservare quando avviene il re-render del componente.

Componente con useState

Questo componente dimostra che l'aggiornamento dello stato tramite useState causa il re-render del componente.

1import React, { useState } from 'react';
2
3const StateCounter: React.FC = () => {
4  const [count, setCount] = useState(0);
5
6  console.log('StateCounter render');
7
8  return (
9    <div>
10      <p>Conto (useState): {count}</p>
11      <button onClick={() => setCount(count + 1)}>Incrementa</button>
12    </div>
13  );
14};

Ogni volta che clicchi il bottone per incrementare il conto, vedrai il messaggio "StateCounter render" nella console, indicando che il componente è stato re-renderizzato.

Componente con useRef

Questo componente dimostra che l'aggiornamento di un ref tramite useRef non causa il re-render del componente.

1import React, { useRef } from 'react';
2
3const RefCounter: React.FC = () => {
4  const count = useRef(0);
5
6  console.log('RefCounter render');
7
8  return (
9    <div>
10      <p>Conto (useRef): {count.current}</p>
11      <button onClick={() => count.current += 1}>Incrementa</button>
12    </div>
13  );
14};

In questo caso, anche se clicchi il bottone per incrementare il conto, il valore visualizzato non cambierà e il messaggio "RefCounter render" apparirà nella console solo una volta al montaggio del componente. Questo perché l'aggiornamento di .current su un ref non causa il re-render del componente.

Dimostrazione in App

Per visualizzare entrambi i componenti e testare il comportamento, puoi inserirli in un'applicazione React principale:

1import React from 'react';
2import StateCounter from './StateCounter'; // Assumi che StateCounter sia definito in StateCounter.tsx
3import RefCounter from './RefCounter'; // Assumi che RefCounter sia definito in RefCounter.tsx
4
5const App: React.FC = () => {
6  return (
7    <div>
8      <StateCounter />
9      <RefCounter />
10    </div>
11  );
12};
13
14export default App;

Questo esperimento dimostra chiaramente che l'aggiornamento dello stato con useState causa il re-render di un componente in React, mentre l'aggiornamento di un valore memorizzato in un useRef non lo fa. useState è usato per dati che cambiano e dovrebbero causare un aggiornamento dell'interfaccia, mentre useRef è utile per mantenere una referenza mutabile attraverso i render senza influenzare il ciclo di re-render del componente.

useRef in breve

useRef è uno strumento essenziale in React per gestire riferimenti diretti agli elementi del DOM e per conservare dati mutabili attraverso i render senza causare aggiornamenti del componente. La sua capacità di fornire un punto di accesso persistente ai dati lo rende unico rispetto ad altri hook di React e apre varie possibilità per la manipolazione del DOM e la gestione dei dati all'interno dei componenti funzionali.

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.