React Hooks sono funzioni che consentono di utilizzare stato, effetti, contesto e altre funzionalità di React in componenti funzionali, senza scrivere classi. Introdotti in React 16.8, gli hook hanno radicalmente cambiato l’approccio allo sviluppo, sostituendo pattern complessi come HOC e render props. Secondo React, 2024, esistono 15 hook integrati che coprono tutti gli scenari di gestione dei componenti.
Punti chiave
React Hooks sono un insieme di funzioni integrate in React che permettono di “collegarsi” ai meccanismi interni del framework senza creare classi. Prima di React 16.8, i componenti funzionali potevano solo accettare props e restituire JSX — qualsiasi logica complessa richiedeva un componente classe.
Il problema dei componenti classe è la suddivisione della logica correlata. Il codice relativo a una stessa entità (ad esempio, una sottoscrizione WebSocket) era forzatamente suddiviso tra componentDidMount, componentDidUpdate e componentWillUnmount. Gli hook risolvono questo: tutta la logica viene raggruppata in un unico useEffect.
Il secondo problema è il riutilizzo della logica. Per riutilizzare la logica di stato tra componenti classe, bisognava applicare HOC (Componenti di Ordine Superiore) o render props — entrambi i pattern creavano annidamento extra nell’albero dei componenti. Gli hook personalizzati risolvono questo senza annidamento.
useState è l’hook più basilare. Prende un valore iniziale e restituisce una tupla: il valore corrente e una funzione per aggiornarlo. A differenza di this.setState nelle classi, useState non fonde gli oggetti — il nuovo stato sostituisce completamente il precedente per una specifica chiamata useState.
import React, { useState } from 'react';
function Form() {
const [text, setText] = useState('');
const [submitting, setSubmitting] = useState(false);
async function handleSubmit() {
setSubmitting(true);
await fetch('/api/submit', {
method: 'POST',
body: JSON.stringify({ text })
});
setSubmitting(false);
}
return (
<div>
<input
value={text}
onChange={e => setText(e.target.value)}
/>
<button onClick={handleSubmit}
disabled={submitting}>
{submitting ? 'Saving...' : 'Submit'}
</button>
</div>
);
}
Il setter di useState può accettare una funzione che riceve il valore precedente e restituisce uno nuovo. La forma funzionale è obbligatoria quando il nuovo stato viene calcolato dal precedente e sono possibili chiamate setState raggruppate — ogni chiamata riceverà il valore precedente corrente.
useEffect è un hook per eseguire effetti collaterali nei componenti funzionali. Prende due argomenti: una funzione effetto e un array di dipendenze. React esegue l’effetto dopo ogni render, ma quando vengono specificate le dipendenze — solo quando una di esse cambia.
Un array di dipendenze vuoto [] significa che l’effetto viene eseguito una volta dopo il primo render (analogo a componentDidMount). Restituire una funzione dall’effetto è una pulizia che viene eseguita allo smontaggio (analogo a componentWillUnmount) e prima di rieseguire l’effetto.
import React, { useState, useEffect } from 'react';
function ChatRoom({ roomId }) {
const [messages, setMessages] = useState([]);
useEffect(() => {
const socket = new WebSocket(`wss://chat/${roomId}`);
socket.onmessage = (event) => {
setMessages(prev => [...prev, JSON.parse(event.data)]);
};
// Pulizia - chiudi socket
return () => socket.close();
}, [roomId]);
return <ul>
{messages.map((msg, i) =>
<li key={i}>{msg.text}</li>
)}
</ul>;
}
Le dipendenze di useEffect sono l’unica connessione tra l’effetto e il mondo esterno. Se l’effetto utilizza una variabile da props o stato, deve essere nell’array di dipendenze. Ignorare questa regola è fonte di bug con closure obsolete (stale closures).
useContext è un hook per leggere i valori del contesto. Prende un oggetto contesto creato da React.createContext e restituisce il valore corrente. Se il contesto cambia, il componente si ri-renderizza automaticamente con il nuovo valore.
Prima di useContext, accedere al contesto nei componenti funzionali richiedeva un avvolgimento in Context.Consumer, che creava annidamento. useContext elimina completamente Consumer — basta chiamare l’hook in qualsiasi punto del componente funzionale.
import React, { createContext, useContext } from 'react';
const ThemeContext = createContext('chiaro');
function ThemedButton() {
const theme = useContext(ThemeContext);
return <button
className={`btn btn-${theme}`}
>
{theme === 'dark' ? 'moon' : '☀️'}
</button>;
}
useContext non è un sostituto di Redux o dello stato globale. React raccomanda di usare il contesto per valori che cambiano raramente — tema, locale, stato di autenticazione. Per dati aggiornati frequentemente, ogni re-render ricalcolerà tutti i consumatori del contesto, il che può ridurre le prestazioni.
useReducer è un’alternativa a useState per stati con logica di aggiornamento complessa. Prende una funzione riduttrice e uno stato iniziale, restituisce lo stato corrente e una funzione dispatch. Il riduttore riceve lo stato corrente e un’azione, restituisce un nuovo stato — una funzione pura senza effetti collaterali.
useReducer è particolarmente utile quando più azioni (set, reset, increment, addItem) gestiscono un unico stato. Invece di molteplici useState con diversi setter — un unico riduttore con tipi di azione chiari. Questo rende la logica prevedibile e facilmente testabile.
import React, { useReducer } from 'react';
function reducer(state, action) {
switch (action.type) {
case 'increment':
return { ...state, count: state.count + 1 };
case 'setStep':
return { ...state, step: action.step };
case 'reset':
return { count: 0, step: 1 };
default:
throw new Error('Unknown action: ' + action.type);
}
}
function Counter() {
const [state, dispatch] = useReducer(reducer, {
count: 0,
step: 1
});
return (
<>
<p>Count: {state.count}</p>
<button
onClick={() => dispatch({ type: 'increment' })}>+</button>
<button
onClick={() => dispatch({ type: 'reset' })}>Reset</button>
</>
);
}
useRef crea un oggetto mutabile che persiste tra i render. Modificare current non attiva un re-render. Gli usi principali sono l’accesso agli elementi DOM e la memorizzazione di valori mutabili (timer, valori precedenti, istanze di librerie).
useMemo memoizza il risultato di una funzione. Ricalcola il valore solo quando una dipendenza cambia. Usalo per calcoli costosi — filtraggio di grandi array, ordinamento, formattazione dati. useCallback è un caso speciale di useMemo per memoizzare funzioni.
import React, { useRef, useMemo, useCallback } from 'react';
function SearchPage({ items, query, onItemSelect }) {
const inputRef = useRef(null);
// Autofocus al mount
useEffect(() => {
inputRef.current?.focus();
}, []);
// Memoizza risultato filtrato
const filtered = useMemo(() => {
return items.filter(item =>
item.name.toLowerCase()
.includes(query.toLowerCase())
);
}, [items, query]);
// Memoizza callback
const handleSelect = useCallback((id) => {
onItemSelect(id);
}, [onItemSelect]);
return (
<div>
<input ref={inputRef}
value={query}
onChange={e => setQuery(e.target.value)}
/>
<ul>
{filtered.map(item =>
<li key={item.id}>
<button
onClick={() => handleSelect(item.id)}>
{item.name}
</button>
</li>
)}
</ul>
</div>
);
}
Una regola importante: useRef, useMemo e useCallback sono strumenti di ottimizzazione, non costrutti obbligatori. Non avvolgere tutto in useMemo — misura prima le prestazioni. L’ottimizzazione prematura complica il codice. React renderizza i componenti rapidamente; useMemo è necessario solo quando i calcoli sono veramente costosi (O(n²) e superiori).
React ha introdotto due regole rigorose per gli hook. Prima: chiama gli hook solo al livello superiore — non all’interno di condizioni, cicli o funzioni annidate. Seconda: chiama gli hook solo da componenti funzionali React o hook personalizzati. Il plugin ESLint eslint-plugin-react-hooks verifica automaticamente entrambe le regole.
L’errore più comune è violare l’ordine di chiamata degli hook. React si basa sull’ordine di chiamata degli hook tra i render. Se un hook si trova all’interno di una condizione e non viene eseguito in un render, tutti gli hook successivi si spostano, causando bug.
| Errore | Cosa succede | Soluzione |
|---|---|---|
| Hook in una condizione | L’ordine degli hook si rompe | Sposta la logica condizionale all’interno dell’hook |
| Stale closure | L’effetto usa un valore vecchio | Aggiungi tutte le dipendenze all’array |
| Ciclo infinito | useEffect senza dipendenze modifica lo stato | Specifica le dipendenze o usa useReducer |
| Mutazione di ref nel render | Effetto collaterale nel corpo della funzione | Sposta le mutazioni in useEffect |
Il secondo errore comune è stale closure in useEffect. Se l’effetto usa una variabile non specificata nelle dipendenze, l’effetto “cattura” il suo valore al momento della creazione della closure. La soluzione è includere sempre tutte le variabili utilizzate nell’array di dipendenze. Se ci sono troppe dipendenze, considera di dividere l’effetto in più parti.
Domande frequenti
React si basa sull’ordine di chiamata degli hook tra i render. Se un hook viene chiamato all’interno di una condizione e non si attiva in un render, tutti gli hook successivi si spostano — il loro stato si mescola. La regola ESLint hooks/exhaustive-deps avvisa su questo.
useLayoutEffect è sincrono e viene eseguito prima che il browser disegni le modifiche. useEffect è asincrono — dopo il disegno. Usa useLayoutEffect solo quando devi misurare o modificare il DOM prima che l’utente veda le modifiche (posizione popover, ripristino dello scroll).
Usa AbortController all’interno di useEffect. Crea un controller, passa il signal a fetch e chiama controller.abort() nella funzione di pulizia. Per browser vecchi, usa un flag cancelled: return () => { cancelled = true; } con un controllo prima di setState.
Un hook personalizzato è una funzione JavaScript con il prefisso use che utilizza hook integrati. Creane uno quando la stessa logica con hook si ripete in due o più componenti. Esempi: useDebounce, useLocalStorage, useMediaQuery. Gli hook personalizzati sostituiscono HOC e render props.
In Strict Mode, React monta e smonta intenzionalmente il componente due volte in sviluppo per trovare bug con effetti non puliti. Se la pulizia è scritta correttamente, la doppia chiamata non causa problemi. Ciò non accade in modalità produzione.
Riepilogo
Svilupperemo un'applicazione mobile chiavi in mano
IT Sectr crea applicazioni iOS e Android per startup e aziende dal 2017. Ti consulteremo e ti proporremo la soluzione migliore.
Leggi anche