Rendering Condizionale e Liste
Rendering Condizionale
In React, il rendering condizionale funziona come le condizioni in JavaScript. Puoi usare if, operatore ternario, && logico e switch per decidere cosa mostrare.
if/else con Return Anticipato
L’approccio più diretto: usa if per restituire JSX diverso in base alla condizione.
function SaluoUtente({ isLoggato, nome }) {
if (!isLoggato) {
return <p>Effettua il login per continuare.</p>;
}
return <h1>Benvenuto, {nome}!</h1>;
}
Il pattern del return anticipato (early return) è particolarmente utile per gestire stati di caricamento, errori e casi limite.
function DettaglioProdotto({ prodotto, caricamento, errore }) {
if (caricamento) {
return <Spinner />;
}
if (errore) {
return <MessaggioErrore messaggio={errore} />;
}
if (!prodotto) {
return <p>Prodotto non trovato.</p>;
}
return (
<div>
<h1>{prodotto.nome}</h1>
<p>{prodotto.descrizione}</p>
<span>{prodotto.prezzo} EUR</span>
</div>
);
}
Operatore Ternario
L’operatore ternario (condizione ? veroJSX : falsoJSX) è perfetto per scegliere tra due alternative direttamente nel JSX.
function StatoOrdine({ stato }) {
return (
<div>
<h2>Il tuo ordine</h2>
{stato === "completato" ? (
<p className="successo">Ordine completato con successo!</p>
) : (
<p className="in-corso">Ordine in elaborazione...</p>
)}
</div>
);
}
Ternari Annidati
I ternari annidati riducono la leggibilità. Evitali quando possibile.
// SCONSIGLIATO: ternari annidati
function Badge({ ruolo }) {
return (
<span>
{ruolo === "admin"
? "Amministratore"
: ruolo === "editor"
? "Editore"
: ruolo === "viewer"
? "Lettore"
: "Ospite"}
</span>
);
}
// MEGLIO: estrai la logica in una funzione o usa una mappa
function Badge({ ruolo }) {
const etichette = {
admin: "Amministratore",
editor: "Editore",
viewer: "Lettore",
};
return <span>{etichette[ruolo] ?? "Ospite"}</span>;
}
Operatore && (AND Logico)
L’operatore && è utile quando vuoi mostrare qualcosa oppure nulla.
function Notifiche({ messaggi, mostraConteggio }) {
return (
<div>
<h2>Notifiche</h2>
{messaggi.length > 0 && (
<ul>
{messaggi.map((msg) => (
<li key={msg.id}>{msg.testo}</li>
))}
</ul>
)}
{mostraConteggio && (
<p>Totale: {messaggi.length}</p>
)}
{messaggi.length === 0 && (
<p>Nessuna notifica.</p>
)}
</div>
);
}
Attenzione al Falsy con &&
Un errore comune: il valore 0 viene renderizzato da React, a differenza di false, null e undefined.
function ListaArticoli({ articoli }) {
return (
<div>
{/* BUG: se articoli.length è 0, renderizza "0" */}
{articoli.length && <Lista articoli={articoli} />}
{/* CORRETTO: converti a booleano */}
{articoli.length > 0 && <Lista articoli={articoli} />}
{/* CORRETTO: doppia negazione */}
{!!articoli.length && <Lista articoli={articoli} />}
</div>
);
}
Switch/Istruzioni Complesse
Per condizioni con molte opzioni, estrai la logica in una funzione separata.
function IconaStato({ stato }) {
const renderIcona = () => {
switch (stato) {
case "successo":
return <IconaCheck className="verde" />;
case "errore":
return <IconaCroce className="rosso" />;
case "caricamento":
return <Spinner />;
case "avviso":
return <IconaAvviso className="giallo" />;
default:
return <IconaInfo className="blu" />;
}
};
return <div className="icona-stato">{renderIcona()}</div>;
}
Pattern Mappa di Componenti
const COMPONENTI_PASSO = {
1: DatiPersonali,
2: Indirizzo,
3: Pagamento,
4: Riepilogo,
};
function Wizard({ passoCorrente }) {
const Componente = COMPONENTI_PASSO[passoCorrente];
if (!Componente) return <p>Passo non valido</p>;
return <Componente />;
}
Rendering di Liste
map() per Renderizzare Liste
Il metodo map() è il modo standard per trasformare un array di dati in un array di elementi React.
function ListaFrutti() {
const frutti = ["Mela", "Banana", "Arancia", "Kiwi", "Pera"];
return (
<ul>
{frutti.map((frutto) => (
<li key={frutto}>{frutto}</li>
))}
</ul>
);
}
Liste con Oggetti
interface Utente {
id: number;
nome: string;
email: string;
attivo: boolean;
}
function ListaUtenti({ utenti }: { utenti: Utente[] }) {
return (
<div className="lista-utenti">
{utenti.map((utente) => (
<div key={utente.id} className="utente-card">
<h3>{utente.nome}</h3>
<p>{utente.email}</p>
<span className={utente.attivo ? "badge-attivo" : "badge-inattivo"}>
{utente.attivo ? "Attivo" : "Inattivo"}
</span>
</div>
))}
</div>
);
}
Estrarre il Componente della Lista
Per liste complesse, estrai l’elemento in un componente separato.
function CardProdotto({ prodotto }) {
return (
<div className="card-prodotto">
<img src={prodotto.immagine} alt={prodotto.nome} />
<h3>{prodotto.nome}</h3>
<p>{prodotto.descrizione}</p>
<span className="prezzo">{prodotto.prezzo.toFixed(2)} EUR</span>
</div>
);
}
function GrigliaProdotti({ prodotti }) {
if (prodotti.length === 0) {
return <p>Nessun prodotto disponibile.</p>;
}
return (
<div className="griglia">
{prodotti.map((prodotto) => (
<CardProdotto key={prodotto.id} prodotto={prodotto} />
))}
</div>
);
}
La Prop key
La prop key è fondamentale per le liste in React. Aiuta React a identificare quali elementi sono cambiati, aggiunti o rimossi, ottimizzando il re-rendering.
Regole per le Key
// CORRETTO: usa un ID unico e stabile
{utenti.map(utente => (
<Utente key={utente.id} {...utente} />
))}
// CORRETTO: stringa unica
{categorie.map(cat => (
<Categoria key={cat.slug} nome={cat.nome} />
))}
// SCONSIGLIATO: indice dell'array (causa problemi con riordinamenti)
{utenti.map((utente, index) => (
<Utente key={index} {...utente} />
))}
// ERRORE: chiave duplicata
{/* Se due utenti hanno lo stesso nome, React si confonde */}
{utenti.map(utente => (
<Utente key={utente.nome} {...utente} />
))}
Quando l’Indice è Accettabile come Key
L’indice è accettabile solo se tutte queste condizioni sono vere:
- La lista è statica (non viene mai riordinata o filtrata)
- Gli elementi non hanno un ID unico
- La lista non viene mai modificata (nessuna aggiunta o rimozione)
Problemi con Key Errate
// BUG: usando l'indice come key con una lista dinamica
function ListaTask() {
const [tasks, setTasks] = useState([
{ id: 1, testo: "Comprare latte" },
{ id: 2, testo: "Studiare React" },
{ id: 3, testo: "Fare esercizio" },
]);
const rimuoviPrimo = () => {
setTasks(tasks.slice(1));
};
return (
<div>
<button onClick={rimuoviPrimo}>Rimuovi primo</button>
{tasks.map((task, index) => (
// Con key={index}, React confonde gli elementi dopo la rimozione
// Con key={task.id}, React sa esattamente quale rimuovere
<input key={task.id} defaultValue={task.testo} />
))}
</div>
);
}
Liste Filtrate e Ordinate
Filtrare Prima di Renderizzare
function ListaProdotti({ prodotti, categoriaFiltro, ordinamento }) {
// Filtra
const prodottiFiltrati = prodotti.filter((p) => {
if (!categoriaFiltro) return true;
return p.categoria === categoriaFiltro;
});
// Ordina
const prodottiOrdinati = [...prodottiFiltrati].sort((a, b) => {
switch (ordinamento) {
case "prezzo-asc":
return a.prezzo - b.prezzo;
case "prezzo-desc":
return b.prezzo - a.prezzo;
case "nome":
return a.nome.localeCompare(b.nome);
default:
return 0;
}
});
return (
<div>
<p>Mostrando {prodottiOrdinati.length} di {prodotti.length} prodotti</p>
<div className="griglia">
{prodottiOrdinati.map((p) => (
<CardProdotto key={p.id} prodotto={p} />
))}
</div>
</div>
);
}
Raggruppare Elementi
function ListaContatti({ contatti }) {
// Raggruppa per lettera iniziale
const gruppati = contatti.reduce((acc, contatto) => {
const lettera = contatto.nome[0].toUpperCase();
if (!acc[lettera]) acc[lettera] = [];
acc[lettera].push(contatto);
return acc;
}, {});
return (
<div>
{Object.entries(gruppati)
.sort(([a], [b]) => a.localeCompare(b))
.map(([lettera, gruppo]) => (
<div key={lettera}>
<h2 className="lettera-gruppo">{lettera}</h2>
<ul>
{gruppo.map((contatto) => (
<li key={contatto.id}>{contatto.nome}</li>
))}
</ul>
</div>
))}
</div>
);
}
Rendering di Liste Vuote
Gestisci sempre il caso di lista vuota per una buona esperienza utente.
function ListaRisultati({ risultati, termineRicerca }) {
if (risultati.length === 0) {
return (
<div className="stato-vuoto">
<img src="/empty-state.svg" alt="" />
<h3>Nessun risultato trovato</h3>
<p>
La ricerca per "{termineRicerca}" non ha prodotto risultati.
Prova con termini diversi.
</p>
</div>
);
}
return (
<ul>
{risultati.map((r) => (
<li key={r.id}>{r.titolo}</li>
))}
</ul>
);
}
Best Practices
- Usa il return anticipato per gestire loading, errori e stati vuoti
- Evita ternari annidati: estrai la logica in funzioni o mappe
- Attenzione al falsy con &&: usa sempre confronti espliciti (
> 0,!== null) - Usa sempre key uniche e stabili nelle liste: preferisci ID dal database o UUID
- Non usare l’indice come key in liste dinamiche
- Filtra e ordina prima di map(): non mischiare logica e rendering
- Gestisci sempre lo stato vuoto delle liste
Riepilogo
Il rendering condizionale e la gestione delle liste sono operazioni quotidiane in React. Scegli il pattern condizionale più leggibile per ogni situazione, usa map() per le liste con key stabili, e gestisci sempre i casi limite come caricamento, errori e liste vuote. Questi pattern formano la base per costruire interfacce dinamiche e robuste.