00
:
00
:
00
:
00
Corso SEO AI - Usa SEOEMAIL al checkout per il 30% di sconto

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 stato
  • setStato: 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>
  );
}

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.