Menu

Liste e key in React: map(), la prop key, key con indice

Per renderizzare una lista in React chiami map() su un array e restituisci JSX per ogni elemento, dando a ciascuno una prop key. Impara filter seguito da map, da dove dovrebbero arrivare le key, e perché le key basate sull'indice dell'array confondono gli elementi quando la lista cambia.

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

Per renderizzare una lista in React, chiama map() su un array e restituisci un pezzo di JSX per ogni elemento. Dai all'elemento più esterno di ogni voce una prop key con un valore che identifica quella voce, di solito il suo ID, così React riesce a distinguere le voci quando la lista cambia.

Aggiungi { id: 'dat', name: 'Date', price: 4 } all'array e compare una quarta riga. JSX non ha una sintassi di ciclo propria: map restituisce un array di elementi, e React renderizza un array renderizzando ogni elemento in ordine.

Renderizzare array con map

map chiama la tua funzione una volta per elemento e raccoglie i risultati in un nuovo array. Dentro le graffe di JSX quell'array è solo un valore, quindi puoi fare il map direttamente nel markup, come sopra, oppure costruire prima l'array:

const items = fruits.map((fruit) => <li key={fruit.id}>{fruit.name}</li>);
return <ul>{items}</ul>;

Due dettagli fanno inciampare spesso. Se usi le graffe nella arrow function, ti serve un return: fruits.map((f) => { return <li key={f.id}>{f.name}</li>; }). Senza, la funzione restituisce undefined e la lista è vuota. E la key va sull'elemento restituito da map, non su un elemento al suo interno. Se la voce è un componente, metti la key sul componente: <FruitRow key={fruit.id} fruit={fruit} />.

Prima filter, poi map

Per mostrare solo alcune voci, filtra prima l'array e poi applica map al risultato. Ogni metodo fa un solo lavoro, e la catena si legge come la frase "i frutti che corrispondono, come voci di lista".

Scrivi er nella casella di ricerca e la lista si riduce a Eraser, Ruler e Stapler; spunta In stock only e sparisce anche Ruler. La lista filtrata viene calcolata durante il rendering a partire dai due pezzi di stato, quindi non c'è un secondo array nello stato da tenere sincronizzato. visible.length === 0 && è un uso sicuro di && perché il confronto è un vero booleano (la pagina sul rendering condizionale mostra cosa succede con uno 0 da solo).

Cosa fanno le key

Quando una lista esegue un nuovo render, React confronta il nuovo array di elementi con quello precedente. Le key sono il modo in cui li abbina: l'elemento con key 3 adesso è l'elemento con key 3 di prima, anche se si è spostato. React mantiene il nodo DOM di quella voce e lo stato del suo componente, e sposta o aggiorna solo ciò che è cambiato. Le voci con key nuove vengono create, e le key sparite vengono rimosse.

Senza key, l'unico criterio con cui React può abbinare è la posizione. Va bene finché nulla si sposta, ed è sbagliato appena qualcosa si sposta.

Perché le key con indice si rompono

map passa l'indice come secondo argomento, quindi key={index} è allettante. L'indice descrive una posizione, non una voce. Inserisci una voce in cima e l'indice di ogni voce si sposta di uno, quindi React crede che la voce 0 sia ancora la voce 0 e ci lascia il suo vecchio stato.

La demo qui sotto renderizza la stessa lista due volte, una con key basate sull'indice e una con key basate sull'ID. Ogni riga ha un input che conserva il proprio testo.

Scrivi math accanto ad Ada in entrambe le liste, poi clicca Add to top. Nella lista con ID la nota resta con Ada. Nella lista con indice la nota salta sulla nuova persona, perché il primo input è ancora l'input in posizione 0, e ora la posizione 0 è qualcun altro. I nomi sono giusti in entrambe le liste, perché arrivano dai dati; va storto solo lo stato che vive nel DOM e nei componenti. Le app reali incontrano questo problema con checkbox, focus, animazioni e qualsiasi componente che contenga useState.

Le key con indice sono sicure solo quando valgono tutte queste condizioni: la lista non viene mai riordinata né filtrata, le voci non vengono mai inserite o rimosse se non in fondo, e le voci non hanno uno stato proprio. Una lista statica di link nel footer va bene. Qualsiasi cosa che un utente possa modificare di solito no.

Da dove arrivano le key

La key migliore è un ID già presente nei dati: un ID del database, il codice di un prodotto, uno slug, un nome utente. I dati che arrivano da un server ne hanno quasi sempre uno.

Per le voci create nel browser, dai a ogni voce un ID quando la crei e salvalo insieme alla voce: un contatore, come nella demo qui sopra, o crypto.randomUUID(). Non generare mai la key durante il rendering:

// Wrong: a new key on every render, so React remounts every item every time
{todos.map((todo) => <Todo key={Math.random()} todo={todo} />)}

// Right: the ID is created once, in the event that adds the item
function addTodo(text) {
    setTodos([...todos, { id: crypto.randomUUID(), text }]);
}

Una key che cambia a ogni rendering è peggio di un indice: ogni voce viene distrutta e ricreata ogni volta, gli input perdono il focus mentre scrivi, e niente conserva il proprio stato. Lo stesso trucco è utile se usato di proposito: cambiare la key di un componente lo azzera, cosa che la pagina su useState usa per svuotare un form.

Le key sono univoche solo tra fratelli

Una key deve essere univoca all'interno di una lista, non in tutta l'app. Due liste separate possono riutilizzare gli stessi ID, perché React confronta le key solo tra i figli dello stesso genitore.

Entrambe le sezioni hanno piatti con key 1 e 2, ed è corretto: ogni ul è una lista a sé. Il problema sono le key duplicate dentro una stessa lista: React non riesce a distinguere le voci in conflitto, e quando la lista si aggiorna potrebbe duplicarle o perderle.

Quando una voce renderizza più elementi fratelli, come un <dt> e un <dd>, racchiudili in <Fragment key={item.id}>. La sintassi breve <> non può ricevere una key.

L'avviso "unique key prop"

Se ometti la key, durante lo sviluppo React stampa questo nella console del browser:

Each child in a list should have a unique "key" prop.

Check the render method of `App`.

La seconda riga nomina il componente il cui rendering ha prodotto la lista, il che ti dice quale map correggere. Due fratelli con la stessa key ricevono un messaggio diverso, che inizia con "Encountered two children with the same key". L'anteprima di questa pagina gira come una build di produzione, che salta quel controllo, quindi qui non vedrai l'avviso nemmeno se elimini una key. Eliminane comunque una nel primo esempio: la lista si renderizza lo stesso, perché una key mancante è un rischio per la correttezza, non un crash. Per risolvere l'avviso, aggiungi una key stabile all'elemento restituito da map; aggiungere key={index} lo zittisce ma riporta il bug mostrato sopra.

Nota anche che key non viene passata al tuo componente. Dentro FruitRow, props.key è undefined. Se il componente ha bisogno dell'ID, passalo una seconda volta come prop normale: <FruitRow key={f.id} id={f.id} />.

Domande frequenti

Perché React ha bisogno di una key sugli elementi di una lista?

Le key dicono a React quale elemento è quale tra un rendering e l'altro. Quando l'array cambia, React abbina gli elementi vecchi e nuovi per key, così può mantenere lo stato e il nodo DOM di ogni elemento con i dati giusti invece di tirare a indovinare in base alla posizione.

Posso usare l'indice dell'array come key?

Solo per una lista che non cambia mai ordine e in cui non vengono mai inseriti o rimossi elementi nel mezzo. Se gli elementi possono spostarsi, una key con indice lega lo stato alla posizione, quindi il testo digitato, le checkbox e il focus finiscono sull'elemento sbagliato.

Cosa dovrei usare come key?

Un ID che appartiene ai dati: un ID del database, il codice di un prodotto, un nome utente. Per gli elementi creati nel browser, genera un ID una sola volta quando crei l'elemento (un contatore o crypto.randomUUID()), mai durante il rendering.

Le key devono essere univoche in tutta l'app?

No. Le key devono essere univoche solo tra gli elementi fratelli della stessa lista. Due liste diverse possono usare le stesse key.

Un componente può leggere la propria key?

No. key è usata da React e non viene passata al componente come prop. Se il componente ha bisogno dell'ID, passalo di nuovo con un altro nome, come id={item.id}.

Come risolvo "Each child in a list should have a unique key prop"?

Aggiungi una key all'elemento più esterno che restituisci da map, usando un ID stabile dell'elemento. Se quell'elemento è un fragment, usa <Fragment key={id}>, perché <> non può ricevere una key.

Illustrazione dei linguaggi di programmazione di Coddy

Impara a programmare con Coddy

INIZIA