Menu

Le regole degli hook in React: perché l'ordine conta

Gli hook hanno due regole: chiamali al livello più alto di un componente, mai dentro condizioni, cicli o funzioni annidate, e chiamali solo da function component o hook personalizzati. Scopri perché React ha bisogno dello stesso ordine di chiamata a ogni rendering e come risolvere gli errori che ricevi quando cambia.

Questa pagina include editor eseguibili: modifica, esegui e vedi subito l'output.

Le regole degli hook sono due: chiama gli hook solo al livello più alto del tuo componente, mai dentro una condizione, un ciclo, una funzione annidata o dopo un return anticipato; e chiamali solo da function component o da hook personalizzati. Esistono perché React distingue gli hook in base all'ordine in cui li chiami, quindi quell'ordine deve essere lo stesso a ogni rendering.

React memorizza i valori degli hook di un componente in una lista collegata a quel componente. A ogni rendering scorre la lista: la prima chiamata a un hook riceve il primo posto, la seconda il secondo posto, e così via. Non ci sono nomi, solo posizioni. L'esempio qui sotto è una versione giocattolo di questa idea in poche righe di JavaScript semplice, non il codice di React, così puoi romperlo senza rischi.

Il primo rendering riempie tre posti: Ada, Countess, 36. Clicca "Render without nickname": la chiamata condizionale viene saltata, quindi la chiamata age prende il posto 1 e legge Countess. Il vero React in alcuni casi si accorge della discrepanza e genera un errore, e in altri consegna il valore sbagliato all'hook sbagliato, il che è peggio. Sposta la chiamata toyUseState('Countess') sopra l'if ed entrambi i pulsanti danno l'età giusta.

Le due regole nel codice

function Profile({ user }) {
    // Good: always called, always in this order
    const [tab, setTab] = useState('posts');
    const theme = useContext(ThemeContext);
    useEffect(() => {
        document.title = user.name;
    }, [user.name]);

    // Bad: inside a condition, a loop or a nested function
    if (user.isAdmin) {
        const [log, setLog] = useState([]); // breaks the order
    }
}

Una chiamata a un hook che viene sempre raggiunta, nello stesso ordine, va bene ovunque stia nel corpo, purché venga prima di qualsiasi return anticipato.

Sistemare un hook chiamato in modo condizionale

Il linter lo segnala come React Hook "useEffect" is called conditionally. React Hooks must be called in the exact same order in every component render.. La soluzione è sempre la stessa: chiama l'hook ogni volta, e metti la condizione al suo interno.

// Broken: the effect exists on some renders and not others
function Notifications({ user }) {
    if (user) {
        useEffect(() => {
            console.log('subscribe for', user.name);
        }, [user]);
    }
    return null;
}

L'effetto viene dichiarato a ogni rendering, e l'if al suo interno decide se fare qualcosa. Effettua l'accesso e l'uscita qualche volta e la Console mostra una sottoscrizione per ogni accesso e una cancellazione per ogni uscita.

Hook dopo un return anticipato

Anche un return anticipato è una condizione. Questo produce "Rendered more hooks than during the previous render" la prima volta che loading diventa false, perché quel rendering raggiunge un hook che quello precedente non ha mai chiamato. Quando un rendering chiama meno hook di quello precedente, il messaggio è "Rendered fewer hooks than expected".

// Broken: useState is skipped while loading
function Comments({ loading, comments }) {
    if (loading) return <p>Loading...</p>;
    const [expanded, setExpanded] = useState(false);
    return <List items={comments} expanded={expanded} />;
}

Ogni hook sta sopra il return, quindi entrambi i rami chiamano gli stessi hook. Clicca "Toggle loading", espandi la lista, poi cambia il caricamento due volte: la lista ricorda di essere stata espansa, perché lo stato non è mai stato saltato.

Hook in un ciclo

Non puoi chiamare useState una volta per ogni voce in un ciclo, perché il numero di voci cambia. Dai a ogni voce il proprio componente e chiama l'hook lì; ogni istanza ha i propri posti.

"Invalid hook call"

Il messaggio inizia con "Invalid hook call. Hooks can only be called inside of the body of a function component.". Ha tre cause abituali:

  1. L'hook viene eseguito mentre React non sta renderizzando un function component. In un gestore di eventi, dentro la callback di un effetto, in un class component o a livello di modulo. Spostalo nel corpo del componente o in un hook personalizzato, e chiama l'hook personalizzato dal componente.
  2. Le versioni di react e react-dom non corrispondono. Installa la stessa versione di entrambi.
  3. Ci sono due copie di React. Un pacchetto locale collegato o una libreria che include il proprio React porta una seconda copia, e gli hook di una copia non funzionano con il renderer dell'altra. Esegui npm ls react e assicurati che compaia una sola versione.
npm ls react

L'anteprima qui gira come una build di produzione, che non stampa questi messaggi: un hook chiamato da un gestore di eventi fallisce con un semplice TypeError come "Cannot read properties of null", e gli errori di React arrivano come brevi codici "Minified React error". Esegui la tua app in modalità di sviluppo per leggere il testo completo.

Lascia che sia il linter a imporle

eslint-plugin-react-hooks controlla entrambe le regole mentre scrivi, e controlla anche gli array delle dipendenze degli effetti. Il template React di Vite e la configurazione ESLint di Next.js lo includono. In una configurazione ESLint flat, attiva il suo preset consigliato:

// eslint.config.js
import reactHooks from 'eslint-plugin-react-hooks';
import { defineConfig } from 'eslint/config';

export default defineConfig([reactHooks.configs.flat.recommended]);

Le due regole principali sono react-hooks/rules-of-hooks (un errore) e react-hooks/exhaustive-deps (un avviso). Dalla versione 7 il preset attiva anche regole che arrivano da React Compiler, come react-hooks/purity e react-hooks/refs, che segnalano rendering impuri e la lettura di ref durante il rendering. Per scegliere le regole una per una, registra il plugin come plugins: { 'react-hooks': reactHooks } ed elenca le regole che vuoi.

Il plugin trova gli hook per nome: qualsiasi funzione il cui nome inizia con use seguito da una lettera maiuscola viene trattata come hook, e solo i componenti (nomi con la maiuscola) e altre funzioni use possono chiamarla. Per questo gli hook personalizzati devono usare il prefisso.

L'eccezione: use

use, aggiunto in React 19, legge una promise o un context, e a differenza di tutti gli altri hook può essere chiamato dentro una condizione o un ciclo. Deve comunque essere chiamato durante il rendering, dentro un componente o un hook.

Quando cambi tema cambia colore solo il titolo con il tema. Prova la stessa cosa con useContext dentro l'if e il linter lo segnala. La pagina sull'hook use spiega come leggere le promise con esso.

Domande frequenti

Quali sono le regole degli hook?

Due regole. Chiama gli hook solo al livello più alto di un function component o di un hook personalizzato, mai dentro condizioni, cicli, funzioni annidate o dopo un return anticipato. E chiama gli hook solo da function component o hook personalizzati, non da funzioni normali o class component.

Perché gli hook non possono essere chiamati in modo condizionale?

React non conosce i tuoi hook per nome. Memorizza i loro valori in una lista e li restituisce nell'ordine in cui avvengono le chiamate. Se una condizione salta una chiamata, ogni hook successivo legge il valore che apparteneva all'hook precedente.

Cosa significa "Rendered more hooks than during the previous render"?

Un rendering ha chiamato più hook del rendering precedente, di solito perché un hook sta dentro un if o dopo un return anticipato che questa volta si è comportato diversamente. Sposta ogni hook sopra la condizione e metti invece la condizione dentro l'hook.

Come risolvo "Invalid hook call"?

Controlla tre cose: che l'hook venga eseguito mentre React renderizza un function component (non in un gestore di eventi, nella callback di un effetto, in un class component o a livello di modulo), che react e react-dom abbiano versioni corrispondenti, e che l'app abbia una sola copia di React (npm ls react le elenca).

L'hook use può essere chiamato in modo condizionale?

Sì. use (React 19) è l'unica eccezione: puoi chiamarlo dentro blocchi if e cicli. Deve comunque essere chiamato dentro un componente o un hook, non in un gestore di eventi.

Illustrazione dei linguaggi di programmazione di Coddy

Impara a programmare con Coddy

INIZIA