useState
Cos’è useState
useState è l’hook fondamentale di React per aggiungere stato locale a un componente funzionale. Lo stato è un valore che, quando cambia, causa il re-rendering del componente per riflettere le modifiche nell’interfaccia.
import { useState } from "react";
function Contatore() {
const [count, setCount] = useState(0);
return (
<div>
<p>Conteggio: {count}</p>
<button onClick={() => setCount(count + 1)}>Incrementa</button>
</div>
);
}
Sintassi e Funzionamento
useState accetta un valore iniziale e restituisce un array con due elementi.
const [stato, setStato] = useState(valoreIniziale);
stato: il valore corrente dello statosetStato: la funzione per aggiornare lo stato (setter)valoreIniziale: il valore con cui lo stato viene inizializzato al primo render
Il nome della variabile e del setter è una convenzione: [x, setX].
const [nome, setNome] = useState("Mario");
const [eta, setEta] = useState(25);
const [isAttivo, setIsAttivo] = useState(false);
const [errori, setErrori] = useState([]);
const [utente, setUtente] = useState(null);
Stato con Valori Primitivi
Numeri
function Contatore() {
const [count, setCount] = useState(0);
return (
<div>
<p>{count}</p>
<button onClick={() => setCount(count + 1)}>+1</button>
<button onClick={() => setCount(count - 1)}>-1</button>
<button onClick={() => setCount(0)}>Reset</button>
</div>
);
}
Stringhe
function CampoNome() {
const [nome, setNome] = useState("");
return (
<div>
<input
value={nome}
onChange={(e) => setNome(e.target.value)}
placeholder="Inserisci il tuo nome"
/>
<p>Ciao, {nome || "sconosciuto"}!</p>
</div>
);
}
Booleani
function ToggleVisibilita() {
const [visibile, setVisibile] = useState(false);
return (
<div>
<button onClick={() => setVisibile(!visibile)}>
{visibile ? "Nascondi" : "Mostra"}
</button>
{visibile && <p>Questo contenuto è ora visibile!</p>}
</div>
);
}
Stato con Oggetti
Quando lo stato è un oggetto, non puoi mutarlo direttamente. Devi sempre creare un nuovo oggetto con le modifiche.
function FormProfilo() {
const [profilo, setProfilo] = useState({
nome: "",
cognome: "",
email: "",
eta: 0,
});
const aggiornacampo = (campo, valore) => {
// Crea un NUOVO oggetto con spread operator
setProfilo({ ...profilo, [campo]: valore });
};
return (
<form>
<input
value={profilo.nome}
onChange={(e) => aggiornaCamera("nome", e.target.value)}
placeholder="Nome"
/>
<input
value={profilo.cognome}
onChange={(e) => aggiornaCamera("cognome", e.target.value)}
placeholder="Cognome"
/>
<input
value={profilo.email}
onChange={(e) => aggiornaCamera("email", e.target.value)}
placeholder="Email"
/>
<pre>{JSON.stringify(profilo, null, 2)}</pre>
</form>
);
}
Oggetti Annidati
Per aggiornare proprietà annidate, lo spread deve essere applicato a ogni livello.
function FormIndirizzo() {
const [dati, setDati] = useState({
nome: "Marco",
indirizzo: {
via: "",
citta: "",
cap: "",
},
});
const aggiornaIndirizzo = (campo, valore) => {
setDati({
...dati,
indirizzo: {
...dati.indirizzo,
[campo]: valore,
},
});
};
return (
<div>
<input
value={dati.indirizzo.via}
onChange={(e) => aggiornaIndirizzo("via", e.target.value)}
placeholder="Via"
/>
<input
value={dati.indirizzo.citta}
onChange={(e) => aggiornaIndirizzo("citta", e.target.value)}
placeholder="Città"
/>
</div>
);
}
Stato con Array
Come per gli oggetti, gli array nello stato devono essere aggiornati in modo immutabile.
function ListaTodo() {
const [todos, setTodos] = useState([]);
const [testo, setTesto] = useState("");
// Aggiungere un elemento
const aggiungi = () => {
if (!testo.trim()) return;
setTodos([...todos, { id: Date.now(), testo, completato: false }]);
setTesto("");
};
// Rimuovere un elemento
const rimuovi = (id) => {
setTodos(todos.filter((todo) => todo.id !== id));
};
// Aggiornare un elemento
const toggleCompletato = (id) => {
setTodos(
todos.map((todo) =>
todo.id === id ? { ...todo, completato: !todo.completato } : todo
)
);
};
// Ordinare
const ordina = () => {
setTodos([...todos].sort((a, b) => a.testo.localeCompare(b.testo)));
};
return (
<div>
<div>
<input value={testo} onChange={(e) => setTesto(e.target.value)} />
<button onClick={aggiungi}>Aggiungi</button>
<button onClick={ordina}>Ordina</button>
</div>
<ul>
{todos.map((todo) => (
<li key={todo.id}>
<span
onClick={() => toggleCompletato(todo.id)}
style={{
textDecoration: todo.completato ? "line-through" : "none",
cursor: "pointer",
}}
>
{todo.testo}
</span>
<button onClick={() => rimuovi(todo.id)}>Elimina</button>
</li>
))}
</ul>
</div>
);
}
Riferimento Rapido: Operazioni Immutabili su Array
| Operazione | Mutabile (NO) | Immutabile (SI) |
|---|---|---|
| Aggiungere | push, unshift |
[...arr, nuovo], [nuovo, ...arr] |
| Rimuovere | splice, pop |
filter |
| Sostituire | arr[i] = x |
map |
| Ordinare | sort |
[...arr].sort |
| Invertire | reverse |
[...arr].reverse() |
Updater Function
Quando il nuovo stato dipende dal valore precedente, usa la updater function (forma funzionale del setter).
function Contatore() {
const [count, setCount] = useState(0);
const incrementaTreVolte = () => {
// SBAGLIATO: count è sempre lo stesso valore (closure)
// Risultato: incrementa di 1, non di 3
setCount(count + 1);
setCount(count + 1);
setCount(count + 1);
// CORRETTO: usa l'updater function
// Ogni chiamata riceve il valore più aggiornato
setCount((prev) => prev + 1);
setCount((prev) => prev + 1);
setCount((prev) => prev + 1);
};
return (
<div>
<p>{count}</p>
<button onClick={incrementaTreVolte}>+3</button>
</div>
);
}
Quando Usare la Updater Function
// Usa la updater quando il nuovo stato dipende dal precedente
setCount((prev) => prev + 1);
setTodos((prev) => [...prev, nuovoTodo]);
setOggetto((prev) => ({ ...prev, campo: nuovoValore }));
// Il setter diretto va bene quando il nuovo stato è indipendente
setNome("Marco");
setVisibile(true);
setSelezionato(null);
Batching degli Aggiornamenti
React raggruppa (batch) più aggiornamenti di stato in un singolo re-render per ottimizzare le performance. Da React 18, il batching è automatico in tutti i contesti.
function FormRegistrazione() {
const [nome, setNome] = useState("");
const [email, setEmail] = useState("");
const [invio, setInvio] = useState(false);
const handleSubmit = () => {
// React 18+: questi tre aggiornamenti causano UN SOLO re-render
setNome("");
setEmail("");
setInvio(true);
};
// Funziona anche in contesti asincroni (React 18+)
const handleFetch = async () => {
const dati = await fetch("/api/utente").then((r) => r.json());
// UN SOLO re-render anche qui
setNome(dati.nome);
setEmail(dati.email);
};
return (
<form onSubmit={(e) => { e.preventDefault(); handleSubmit(); }}>
<input value={nome} onChange={(e) => setNome(e.target.value)} />
<input value={email} onChange={(e) => setEmail(e.target.value)} />
<button type="submit">Invia</button>
</form>
);
}
Inizializzazione Lazy dello Stato
Se il calcolo del valore iniziale è costoso, passa una funzione a useState per eseguirlo solo al primo render.
// SBAGLIATO: calcolaValore() viene eseguito ad OGNI render
const [dati, setDati] = useState(calcolaValoreCostoso());
// CORRETTO: la funzione viene eseguita solo al PRIMO render
const [dati, setDati] = useState(() => calcolaValoreCostoso());
Esempio Pratico
function Editor() {
// Leggi localStorage solo al primo render
const [contenuto, setContenuto] = useState(() => {
const salvato = localStorage.getItem("bozza");
return salvato ? JSON.parse(salvato) : "";
});
return (
<textarea
value={contenuto}
onChange={(e) => setContenuto(e.target.value)}
/>
);
}
Più Variabili di Stato vs Oggetto Unico
Variabili Separate (Consigliato per Stato Indipendente)
function Form() {
const [nome, setNome] = useState("");
const [email, setEmail] = useState("");
const [accettaTermini, setAccettaTermini] = useState(false);
// Ogni campo è indipendente e ha il suo setter dedicato
}
Oggetto Unico (Utile per Stato Correlato)
function FormRegistrazione() {
const [form, setForm] = useState({
nome: "",
email: "",
password: "",
});
const aggiornaForm = (campo: string, valore: string) => {
setForm((prev) => ({ ...prev, [campo]: valore }));
};
}
Quando lo stato diventa complesso con molti campi correlati, considera useReducer al posto di useState.
Errori Comuni
Mutare lo Stato Direttamente
// SBAGLIATO
const [utente, setUtente] = useState({ nome: "Marco", eta: 28 });
utente.nome = "Luca"; // Mutazione diretta!
setUtente(utente); // React non rileva il cambiamento
// CORRETTO
setUtente({ ...utente, nome: "Luca" });
Stato Asincrono nelle Closure
function Timer() {
const [secondi, setSecondi] = useState(0);
const avvia = () => {
setInterval(() => {
// SBAGLIATO: secondi è sempre 0 (closure stale)
// setSecondi(secondi + 1);
// CORRETTO: usa l'updater function
setSecondi((prev) => prev + 1);
}, 1000);
};
return (
<div>
<p>{secondi} secondi</p>
<button onClick={avvia}>Avvia</button>
</div>
);
}
Riepilogo
useState è l’hook più usato in React. Ricorda di aggiornare lo stato in modo immutabile (specialmente oggetti e array), usa la updater function quando il nuovo stato dipende dal precedente, e sfrutta l’inizializzazione lazy per calcoli costosi. Il batching automatico di React 18 garantisce che più aggiornamenti di stato producano un singolo re-render.