Errori e fix

React key warning: perché compare e quando l'indice è sbagliato

React chiede una key stabile per ogni elemento di una lista. L'indice funziona solo se la lista non viene riordinata, filtrata o arricchita.

In sintesi

  • La key deve identificare l'elemento, non la sua posizione: usa l'id del record.
  • L'indice va bene solo per liste statiche che non cambiano ordine.
  • Una key duplicata non dà errore ma produce stato condiviso tra due righe.

Risposta breve: React ha bisogno di una key stabile per riconoscere un elemento tra due render. Usa l'id del record. L'indice va bene solo per una lista statica che non cambia ordine.

Il messaggio esatto

Warning: Each child in a list should have a unique "key" prop.
Check the render method of `OrderList`. See
https://react.dev/link/warning-keys for more information.

Perché una key e non un indice

ListaIndiceId stabile
statica, mai riordinataaccettabilepreferibile
filtrabilestato sbagliato sulle righecorretto
riordinabileinput e checkbox incoerenticorretto
con elementi rimossistato che migracorretto

React non usa la key per il DOM: la usa per decidere quale istanza di componente conservare. Con l'indice, la riga 2 resta la riga 2 anche quando il record cambia, e lo stato locale, testo digitato, checkbox, focus, resta agganciato alla posizione.

Il meccanismo: la key decide quale istanza sopravvive

Il reconciler confronta i figli di un elemento per posizione e per key. Quando la key cambia, React non aggiorna l'istanza: la smonta e ne crea una nuova. Smontare significa perdere lo stato locale, rieseguire gli effetti e sostituire il nodo del DOM. Per questo una key instabile non è un dettaglio di stile: è la differenza tra aggiornare una riga e ricostruire l'intera lista a ogni render. L'avviso che hai visto compare solo in sviluppo; in produzione il difetto resta e non stampa nulla.

Un passo diagnostico per le key duplicate

L'avviso di React copre la key mancante, non quella duplicata: due record con lo stesso id producono silenzio. Il controllo qui sotto costa una funzione e va tenuto in sviluppo, non nel bundle di produzione: fallisce al primo valore ambiguo e ti dice quale si ripete. Se lo lanci e non fallisce, il problema non è nel dataset ma nel livello che lo trasforma: un map che riscrive l'id, una concatenazione con un indice, una chiave composta da campi modificabili.

// solo in sviluppo: fallisce subito su una key duplicata
function assertUniqueKeys(rows: T[], keyOf: (row: T) => string) {
  const seen = new Set();
  for (const row of rows) {
    const key = keyOf(row);
    if (seen.has(key)) throw new Error(`key duplicata: ${key}`);
    seen.add(key);
  }
  return rows;
}

La correzione

type Order = { id: string; total: number };

export function OrderList({ orders }: { orders: Order[] }) {
  return (
    
      {orders.map((order) => (
        {order.total} €
      ))}
    
  );
}

Se il record non ha un id, generane uno stabile alla creazione, non nel render. crypto.randomUUID() dentro il componente produce una key nuova a ogni render e React ricrea tutto: è peggio dell'indice.

Frammenti e componenti

import { Fragment } from "react";

{rows.map((row) => (
  
    {row.label}
    {row.value}
  
))}

La sintassi abbreviata <>...</> non accetta attributi: serve la forma esplicita. Lo stesso vale per un componente che restituisce più elementi.

Quando la correzione non funziona

Se l'avviso resta, la key manca su un livello intermedio: un wrapper dentro il map che restituisce un array senza key. Se invece vedi comportamenti strani senza avvisi, cerca key duplicate: due record con lo stesso id non producono errore, ma React riusa la stessa istanza per entrambi.

Quando la correzione comune peggiora le cose

La correzione istintiva sostituisce l'indice con qualcosa di unico generato al render: key={crypto.randomUUID()} oppure key={JSON.stringify(row)}. La prima cambia a ogni render e rimonta ogni riga, quindi il testo digitato sparisce, il focus si perde e le transizioni ripartono. La seconda sembra stabile ma dipende dall'intero contenuto: ogni carattere scritto in una riga editabile ne cambia la forma e rimonta la riga proprio mentre l'utente scrive. In entrambi i casi l'avviso sparisce e il comportamento peggiora. Una key deve essere stabile tra i render e unica tra i fratelli, e solo l'id del record soddisfa entrambe le condizioni.

Un limite con un nome: id non univoci

Se il dataset contiene due record con lo stesso id, nessuna key li separa. Succede con i dati aggregati: due righe che arrivano dallo stesso ordine, un elenco che unisce i risultati di due ricerche, un join senza distinct. React non può inventare l'unicità che i dati non hanno, e il sintomo è una riga che eredita lo stato dell'altra. Il nome del limite è questo: la key eredita i difetti della sorgente. La correzione sta a monte, nell'identificatore con cui il record nasce, non nel markup.

Come prevenirlo

  • Nel modello dati, ogni record ha un id stabile prima di arrivare al componente.
  • Mai Math.random() o Date.now() come key.
  • Nei test, riordina la lista e verifica che i valori digitati seguano il record, non la posizione.

Una regola ESLint chiude il caso della key mancante, non quello della key sbagliata. Per il secondo serve un test che riordina la lista e verifica che i valori digitati seguano il record: è l'unico modo per accorgersi di una key che compila, non avvisa e sbaglia.

Dove continuare

Nel catalogo: data table con preview, componenti e blocchi. Le guide: checklist accessibilità e alternative a MUI; il confronto: Radix, Base UI e React Aria.

Domande frequenti

Perché React non usa l'indice come default?

Perché l'indice cambia quando la lista cambia: React riuserebbe lo stato sbagliato su un'altra riga, producendo input e checkbox incoerenti.

La key è visibile nel DOM?

No. La key è un'istruzione per il reconciler e non viene scritta come attributo HTML.

Come do la key a un Fragment?

Con la forma esplicita Fragment, passando key come prop: la sintassi abbreviata non accetta attributi. La key non arriva al componente, quindi va letta fuori dalle props.

Due elementi con la stessa key cosa causano?

React avvisa in sviluppo e può riusare lo stato nel posto sbagliato. Il sintomo tipico è un input che conserva il valore della riga precedente.

Serve una key sulle liste statiche?

Sì, React la chiede comunque. Su una lista davvero statica l'indice è accettabile, ma una key semantica resta più chiara.