React per Principianti: Guida Completa per Iniziare a Sviluppare Web App

Principiante
JavaScript React

Impara le basi di React, la libreria JavaScript più popolare per costruire interfacce utente interattive. Questa guida completa ti accompagnerà dai primi passi alla creazione della tua prima applicazione web, spiegando ogni concetto in modo chiaro e con esempi pratici.

Pubblicato
Tag
Web Development Beginner javascript frontend tutorial React Componenti JSX Hooks

Introduzione a React: Perché è così popolare e come ti cambia la vita di sviluppatore

Benvenuti nel mondo entusiasmante dello sviluppo web moderno! Se sei qui, probabilmente hai già sentito parlare di React, o React.js. Ma cos'è esattamente? React è una libreria JavaScript open-source per la costruzione di interfacce utente (UI) interattive e dinamiche. È stata sviluppata da Facebook (ora Meta) e rilasciata nel 2013, diventando rapidamente uno degli strumenti più amati e utilizzati dagli sviluppatori frontend di tutto il mondo.

Cos'è React e perché dovresti usarlo?

La prima cosa da capire è che React è una libreria, non un framework completo. Questo significa che si concentra principalmente sulla "V" di MVC (Model-View-Controller), ovvero sulla visualizzazione dell'interfaccia utente. A differenza di un framework come Angular che offre una soluzione "all-in-one" con un'architettura predefinita, React ti dà più libertà su come strutturare il resto della tua applicazione (routing, gestione dello stato, fetching dati, ecc.).

Ma perché React è così popolare e perché dovresti impararlo?

  • Approccio basato sui Componenti: React ti incoraggia a costruire la tua UI come un insieme di componenti isolati, riutilizzabili e indipendenti. Pensa a un componente come a un piccolo blocco Lego: puoi costruire un bottone, una barra di navigazione, una card prodotto, e poi combinarli per creare interfacce complesse. Questo approccio rende il codice più modulare, facile da gestire e da testare.
  • Virtual DOM: Questa è una delle caratteristiche chiave che rende React così performante. Invece di manipolare direttamente il DOM (Document Object Model) del browser, che può essere lento, React crea una copia leggera del DOM in memoria, chiamata Virtual DOM. Quando lo stato dell'applicazione cambia, React confronta il Virtual DOM attuale con quello precedente, calcola le differenze e aggiorna solo le parti del DOM reale che sono effettivamente cambiate. Questo processo, chiamato "riconciliazione", minimizza le operazioni sul DOM reale, rendendo le applicazioni incredibilmente veloci e reattive.
  • JSX (JavaScript XML): È una sintassi estensione per JavaScript che ti permette di scrivere codice simile all'HTML direttamente all'interno dei tuoi file JavaScript. Sembra strano all'inizio, ma rende la scrittura di componenti UI molto più intuitiva e leggibile, combinando la logica di visualizzazione con la logica del componente stesso.
  • Grande Comunità e Ecosistema: Essendo una delle librerie più diffuse, React vanta una comunità enorme e attiva. Questo significa che troverai facilmente risorse, tutorial, risposte ai problemi e un'ampia gamma di librerie e strumenti di terze parti per estendere le funzionalità della tua applicazione.
  • Performance e Scalabilità: Grazie al Virtual DOM e all'ottimizzazione del rendering, le applicazioni React sono intrinsecamente performanti. L'approccio a componenti le rende anche altamente scalabili, permettendo di gestire progetti di qualsiasi dimensione con maggiore facilità.

In questa guida, ti accompagneremo passo dopo passo attraverso i concetti fondamentali di React, partendo dalla configurazione dell'ambiente fino alla creazione di una piccola applicazione interattiva. Preparati a scoprire un modo potente ed efficiente per costruire interfacce utente moderne!

Preparazione dell'Ambiente di Sviluppo: Il tuo laboratorio React

Prima di poter scrivere la tua prima riga di codice React, è necessario configurare l'ambiente di sviluppo. Non preoccuparti, il processo è piuttosto semplice e ti permetterà di avviare rapidamente un nuovo progetto.

Node.js e npm/yarn: i tuoi alleati

React è una libreria JavaScript, e per sviluppare applicazioni React avrai bisogno di Node.js. Node.js è un runtime JavaScript che ti permette di eseguire codice JavaScript al di fuori del browser. Ma perché ci serve per React?

  • npm (Node Package Manager) o Yarn: Quando installi Node.js, viene installato anche npm (o puoi installare Yarn separatamente). Questi sono gestori di pacchetti che ti permettono di scaricare e gestire tutte le dipendenze (librerie, strumenti, ecc.) necessarie per il tuo progetto React. React stesso e tutte le sue dipendenze sono pacchetti npm/yarn.
  • Strumenti di build: I progetti React moderni utilizzano strumenti come Webpack e Babel per compilare il codice JSX in JavaScript standard che i browser possono capire, e per raggruppare tutti i file in un bundle ottimizzato. Questi strumenti vengono eseguiti tramite Node.js.

Installazione di Node.js

  1. Visita il sito ufficiale: Vai su nodejs.org.

  2. Scarica la versione LTS: Ti consigliamo di scaricare la versione LTS (Long Term Support), che è la più stabile e raccomandata per la maggior parte degli utenti. Segui le istruzioni di installazione per il tuo sistema operativo (Windows, macOS, Linux).

  3. Verifica l'installazione: Dopo l'installazione, apri il tuo terminale o prompt dei comandi ed esegui questi comandi per verificare che Node.js e npm siano stati installati correttamente:

    node -v
    npm -v
    

    Dovresti vedere i numeri di versione di Node.js e npm. Se vuoi usare Yarn (un'alternativa più veloce a npm), puoi installarlo con npm install -g yarn e poi verificare con yarn -v.

Create React App (CRA): il modo più semplice per iniziare

Configurare un progetto React da zero con tutti gli strumenti di build (Webpack, Babel, ecc.) può essere complesso per i principianti. Fortunatamente, esiste uno strumento fantastico chiamato Create React App (CRA). CRA è un toolkit ufficiale mantenuto da Facebook che ti permette di creare un nuovo progetto React con una configurazione di sviluppo moderna e pronta all'uso, senza dover configurare manualmente nulla. Pensalo come un "generatore di progetti React" che ti fornisce una base solida su cui costruire.

Creare il tuo primo progetto React con CRA

  1. Apri il terminale: Naviga nella cartella in cui desideri creare il tuo progetto.

  2. Crea una nuova app React: Esegui il seguente comando nel terminale:

    npx create-react-app il-mio-primo-progetto-react
    
    • npx è un tool che ti permette di eseguire pacchetti npm senza installarli globalmente. In questo caso, esegue create-react-app.
    • il-mio-primo-progetto-react sarà il nome della tua cartella di progetto. Puoi scegliere qualsiasi nome tu voglia (solo minuscole, numeri e trattini).

    Questo comando creerà una nuova directory con il nome specificato, scaricherà tutte le dipendenze necessarie e configurerà la tua applicazione React.

  3. Naviga nella directory del progetto: Una volta completato il processo (potrebbe volerci qualche minuto), entra nella cartella del tuo progetto:

    cd il-mio-primo-progetto-react
    
  4. Avvia l'applicazione: Esegui il comando per avviare il server di sviluppo:

    npm start
    

    o, se usi Yarn:

    yarn start
    

    Questo aprirà automaticamente il tuo browser all'indirizzo http://localhost:3000 (o una porta diversa se la 3000 è occupata), mostrando l'applicazione React predefinita.

Struttura di un progetto React generato da CRA

Quando apri la cartella del tuo progetto con un editor di codice (come VS Code), vedrai una struttura simile a questa:

my-app/
├── node_modules/         # Tutte le dipendenze del progetto
├── public/               # File statici (index.html, immagini, manifest, favicon)
│   ├── index.html        # Il file HTML principale della tua applicazione
│   └── ...
├── src/                  # Il cuore della tua applicazione React
│   ├── App.css           # Fogli di stile per il componente App
│   ├── App.js            # Il componente principale della tua applicazione
│   ├── App.test.js       # File di test per App.js
│   ├── index.css         # Fogli di stile globali
│   ├── index.js          # Il punto di ingresso della tua applicazione React
│   ├── logo.svg          # Un'immagine logo
│   ├── reportWebVitals.js # Per misurare le prestazioni web
│   └── setupTests.js     # Configurazione per i test
├── .gitignore            # File e cartelle da ignorare da Git
├── package.json          # Metadati del progetto e lista delle dipendenze
├── package-lock.json     # Dettagli precisi delle dipendenze (npm)
└── README.md             # Informazioni sul progetto

La cartella src/ è dove passerai la maggior parte del tuo tempo. In particolare, index.js è il punto di ingresso dell'applicazione, dove il tuo componente React principale viene "montato" nel index.html.

index.js è il file che inizializza la tua applicazione React. Contiene il codice che renderizza il tuo componente principale (App in questo caso) nel DOM del browser. Ecco come appare:

// src/index.js
import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
  <React.StrictMode>
    <App />
  </React.StrictMode>
);

// Se vuoi iniziare a misurare le prestazioni della tua app, passa una funzione
// per registrare i risultati (es. reportWebVitals(console.log))
// o inviarli a un endpoint di analytics.
reportWebVitals();

Come puoi vedere, ReactDOM.createRoot crea una "root" React e root.render renderizza il componente <App /> al suo interno. Il componente <App /> è importato da App.js.

I Tuoi Primi Passi con i Componenti React e JSX

Il concetto centrale in React è il componente. Tutto in React è un componente. Immagina un'interfaccia utente come un puzzle, dove ogni pezzo è un componente. Ogni componente ha una sua logica e una sua parte di UI, e insieme formano l'intera applicazione.

Cosa sono i Componenti?

I componenti sono i blocchi costruttivi fondamentali di qualsiasi applicazione React. Ti permettono di suddividere l'interfaccia utente in parti indipendenti e riutilizzabili. Questo rende il codice più facile da capire, mantenere e sviluppare in team. Ad esempio, una pagina web potrebbe essere composta da un componente Header, un componente Sidebar, un componente MainContent e diversi componenti Card all'interno del MainContent.

In React, esistono principalmente due tipi di componenti:

  1. Componenti a Funzione (Functional Components): Sono semplici funzioni JavaScript che accettano un oggetto props (proprietà) come argomento e restituiscono elementi React (generalmente JSX). Con l'introduzione degli Hooks, sono diventati il modo preferito per scrivere componenti in React, anche quelli che gestiscono lo stato interno.
  2. Componenti a Classe (Class Components): Sono classi JavaScript che estendono React.Component e devono implementare un metodo render() che restituisce elementi React. Prima degli Hooks, erano l'unico modo per avere uno stato interno e metodi del ciclo di vita. Oggi sono meno comuni, ma potresti incontrarli in codice legacy.

Per i principianti, ci concentreremo sui componenti a funzione con gli Hooks, in quanto sono più moderni, più semplici da scrivere e più facili da capire.

JSX: JavaScript XML

JSX è una sintassi che estende JavaScript, permettendoti di scrivere markup simile all'HTML direttamente all'interno del tuo codice JavaScript. Sembra una miscela di HTML e JavaScript, e inizialmente potrebbe sembrare strano, ma è incredibilmente potente per descrivere come dovrebbe apparire la tua UI.

Perché JSX?

  • Leggibilità: Permette di visualizzare la struttura dell'UI in modo più chiaro e intuitivo rispetto alla creazione di elementi DOM con JavaScript puro.
  • Espressioni JavaScript: Puoi incorporare espressioni JavaScript all'interno del JSX usando le parentesi graffe {}. Questo rende facile inserire dati dinamici, chiamare funzioni o eseguire logica condizionale direttamente nel tuo markup.
  • Sicurezza: React e JSX prevengono attacchi di iniezione (come XSS) eseguendo automaticamente l'escape di qualsiasi valore embeddato nel JSX prima di renderizzarlo.
  • Compilazione: Il codice JSX non viene compreso direttamente dal browser. Viene "transpilato" (convertito) in chiamate a React.createElement() da strumenti come Babel durante il processo di build.

Regole di base di JSX:

  1. Un solo elemento root: Ogni componente React deve restituire un singolo elemento root. Se devi restituire più elementi adiacenti, avvolgili in un elemento padre (come un <div>) o usa un Fragment (<></> o <React.Fragment>).
  2. Attributi in camelCase: Gli attributi HTML come class diventano className in JSX, e for diventa htmlFor. Questo perché class e for sono parole chiave riservate in JavaScript.
  3. Tag auto-chiudenti: I tag HTML che non hanno un contenuto (come <img>, <input>, <br/>) devono essere esplicitamente chiusi con uno slash finale (es. <img src="..." />).
  4. Espressioni JavaScript: Per inserire codice JavaScript dinamico, usa le parentesi graffe {}. Ad esempio, {nomeVariabile} o {unaFunzione()}.

Creazione del tuo primo componente funzionale

Apri il file src/App.js nel tuo progetto il-mio-primo-progetto-react. Questo è il componente principale che viene renderizzato da index.js. Lo modificheremo per capire come funziona.

// src/App.js
import React from 'react'; // Non è strettamente necessario importare React con le versioni più recenti, ma è una buona pratica
import './App.css'; // Importiamo i fogli di stile per questo componente

// Questo è un componente a funzione
function App() {
  const nome = "Mondo";

  return (
    <div className="App">
      <header className="App-header">
        <h1>Ciao, {nome}! Benvenuto nel tuo primo componente React.</h1>
        <p>
          Stiamo imparando a sviluppare con React.
        </p>
        <button => alert('Hai cliccato il bottone!')}>
          Cliccami!
        </button>
      </header>
    </div>
  );
}

export default App; // Esportiamo il componente per poterlo usare altrove

Spiegazione del codice:

  • import React from 'react';: Anche se con le versioni più recenti di React non è sempre obbligatorio importare React quando si usa JSX (grazie a una trasformazione automatica), è comunque una buona pratica e non fa male includerlo.
  • import './App.css';: Stiamo importando un file CSS specifico per questo componente. Questo è un esempio di come React ti permetta di incapsulare stili insieme alla logica del componente.
  • function App() { ... }: Questa è la definizione del nostro componente a funzione. Il nome del componente deve iniziare con una lettera maiuscola (convenzione React).
  • const nome = "Mondo";: Qui definiamo una semplice variabile JavaScript.
  • return (...): Il componente restituisce il markup JSX che descrive la sua UI.
  • className="App": In JSX, usiamo className invece di class per assegnare classi CSS.
  • <h1>Ciao, {nome}! ...</h1>: Qui vediamo un esempio di espressione JavaScript all'interno del JSX. La variabile nome viene inserita dinamicamente nel titolo.
  • <button => alert('Hai cliccato il bottone!')}>: Questo è un gestore di eventi. onClick è un attributo JSX che accetta una funzione JavaScript. Quando il bottone viene cliccato, la funzione alert verrà eseguita.
  • export default App;: Questa riga rende il componente App disponibile per essere importato e utilizzato in altri file JavaScript (come index.js).

Salva il file App.js. Se il tuo server di sviluppo è ancora in esecuzione (npm start), vedrai automaticamente gli aggiornamenti nel browser. Congratulazioni, hai modificato il tuo primo componente React!

Gestire i Dati: Props e State in React

Un'applicazione web non è statica; deve interagire con i dati. In React, ci sono due modi principali per gestire i dati all'interno dei componenti: le Props e lo State. Comprendere la differenza tra questi due è fondamentale per costruire applicazioni React efficaci.

Props (Properties): Dati in entrata

Le Props sono un meccanismo per passare dati da un componente padre a un componente figlio. Pensa alle props come agli argomenti di una funzione. Un componente padre "passa" delle proprietà (props) ai suoi componenti figli, e i figli "ricevono" queste proprietà per visualizzare informazioni o per comportarsi in un certo modo.

Caratteristiche delle Props:

  • Unidirezionali: I dati fluiscono in una sola direzione: dal componente padre al figlio. Un componente figlio non può modificare direttamente le props che ha ricevuto dal padre. Sono "read-only".
  • Immutabili: Una volta che un componente riceve delle props, non può cambiarle. Se il padre vuole che le props cambino, deve passare nuove props al figlio.
  • Riutilizzabilità: Le props rendono i componenti altamente riutilizzabili. Puoi creare un componente generico (es. un bottone) e poi personalizzarlo passandogli props diverse (es. testo del bottone, colore, funzione onClick).

Come passare e ricevere Props:

Creiamo un nuovo componente Saluto in src/Saluto.js.

// src/Saluto.js
import React from 'react';

function Saluto(props) { // Il componente riceve un oggetto 'props' come argomento
  return (
    <h2>Ciao, {props.nome}!</h2>
  );
}

export default Saluto;

Ora, usiamo questo componente Saluto all'interno di App.js e passiamogli una prop nome:

// src/App.js (modificato)
import React from 'react';
import './App.css';
import Saluto from './Saluto'; // Importiamo il nostro nuovo componente Saluto

function App() {
  const utente = "Alice";

  return (
    <div className="App">
      <header className="App-header">
        <h1>Benvenuto nella mia App React!</h1>
        {/* Passiamo la prop 'nome' al componente Saluto */}
        <Saluto nome={utente} />
        <Saluto nome="Bob" /> {/* Possiamo riutilizzare il componente con props diverse */}
        <Saluto nome="Charlie" />
        <p>
          Questi sono saluti dinamici grazie alle props.
        </p>
      </header>
    </div>
  );
}

export default App;

Salva entrambi i file. Vedrai ora tre saluti diversi renderizzati, ognuno con un nome passato come prop. È chiaro come il componente Saluto sia generico e il suo contenuto sia determinato dalle props che riceve.

State (Stato): Dati interni mutabili

Lo State è un oggetto JavaScript che contiene dati specifici per un componente e che possono cambiare nel tempo. Quando lo stato di un componente cambia, React "re-renderizza" quel componente e i suoi figli, aggiornando l'interfaccia utente per riflettere i nuovi dati.

Caratteristiche dello State:

  • Interno al componente: Lo stato è gestito all'interno del componente in cui è definito. Non è direttamente accessibile da altri componenti (a meno che non venga passato tramite props).
  • Mutabile (ma con regole): Lo stato può e deve essere modificato. Tuttavia, React ha un modo specifico per farlo: devi usare una funzione speciale fornita da React (con gli Hooks, è useState). Non modificare mai lo stato direttamente! Questo è un errore comune che può portare a comportamenti inaspettati.
  • Trigger di re-render: Un cambiamento nello stato di un componente fa sì che React esegua nuovamente la funzione del componente (o il metodo render per i componenti a classe) e aggiorni il DOM virtuale, e di conseguenza il DOM reale se ci sono differenze.

L'Hook useState

Per gestire lo stato nei componenti a funzione, React ci fornisce gli Hooks. Il più fondamentale tra questi è useState.

useState è una funzione che accetta un argomento: il valore iniziale dello stato. Restituisce un array contenente due elementi:

  1. Il valore corrente dello stato.
  2. Una funzione per aggiornare lo stato.

Vediamo un esempio classico: un contatore.

// src/Contatore.js (nuovo file)
import React, { useState } from 'react'; // Importiamo l'Hook useState

function Contatore() {
  // Dichiarazione di una variabile di stato 'conteggio'
  // 'conteggio' è il valore attuale, 'setConteggio' è la funzione per aggiornarlo
  // Il valore iniziale è 0
  const [conteggio, setConteggio] = useState(0);

  const incrementa = () => {
    setConteggio(conteggio + 1); // Usiamo setConteggio per aggiornare lo stato
  };

  const decrementa = () => {
    setConteggio(conteggio - 1); // Anche qui, usiamo la funzione di aggiornamento
  };

  return (
    <div>
      <h3>Contatore: {conteggio}</h3>
      <button
      <button
    </div>
  );
}

export default Contatore;

Ora, integra Contatore in App.js:

// src/App.js (modificato)
import React from 'react';
import './App.css';
import Saluto from './Saluto';
import Contatore from './Contatore'; // Importiamo il Contatore

function App() {
  return (
    <div className="App">
      <header className="App-header">
        <h1>Benvenuto nella mia App React!</h1>
        <Saluto nome="Alice" />
        <Saluto nome="Bob" />
        <hr/> {/* Linea orizzontale per separare gli esempi */}
        <Contatore /> {/* Inseriamo il nostro contatore */}
      </header>
    </div>
  );
}

export default App;

Salva e prova il contatore. Noterai che cliccando i bottoni, il numero cambia e l'interfaccia si aggiorna automaticamente. Questo è il potere dello stato in React!

Differenze chiave tra Props e State:

Caratteristica Props State
Gestione Passate dal componente padre al figlio Gestito internamente dal componente
Mutabilità Immutabili (read-only) Mutabile (modificabile con useState)
Scopo Configurare il componente o passargli dati Gestire dati che cambiano nel tempo
Flusso Unidirezionale (padre -> figlio) Interno al componente (ma può influire sui figli tramite props)

Interazione Utente e Gestione degli Eventi

Le applicazioni web moderne sono interattive. Gli utenti cliccano bottoni, digitano in campi di testo, muovono il mouse. React fornisce un sistema robusto per gestire questi eventi utente in modo dichiarativo, simile a come si farebbe con l'HTML tradizionale, ma con alcune importanti differenze.

Come funzionano gli eventi in React

React usa un sistema di eventi sintetici. Questo significa che React avvolge gli eventi nativi del browser in un proprio oggetto SyntheticEvent che garantisce la compatibilità cross-browser. Non devi preoccuparti delle differenze tra i browser per la gestione degli eventi; React lo fa per te.

Sintassi per la gestione degli eventi:

In JSX, i gestori di eventi sono specificati come attributi con la convenzione camelCase. Ad esempio, onclick in HTML diventa onClick in React, onchange diventa onChange, e così via.

Il valore dell'attributo dell'evento deve essere una funzione JavaScript, non una stringa.

// Esempio di gestore di eventi
<button

Dove handleClick è una funzione definita all'interno del tuo componente.

Esempio: un bottone che cambia un testo

Riprendiamo il nostro componente App.js e aggiungiamo un testo che cambia quando clicchiamo un bottone.

// src/App.js (modificato)
import React, { useState } from 'react'; // Importiamo useState
import './App.css';

function App() {
  const [messaggio, setMessaggio] = useState('Clicca il bottone per cambiare il messaggio!');

  // Funzione che verrà chiamata quando il bottone viene cliccato
  const gestisciClick = () => {
    setMessaggio('Il messaggio è stato cambiato!');
  };

  // Funzione che riceve l'oggetto evento (ad esempio, per un input)
  const gestisciInput = (event) => {
    setMessaggio(`Hai digitato: ${event.target.value}`);
  };

  return (
    <div className="App">
      <header className="App-header">
        <h1>Gestione degli Eventi in React</h1>
        <p>{messaggio}</p>
        <button
          Cambia Messaggio
        </button>
        
        <br/><br/>

        <input 
          type="text" 
          placeholder="Digita qualcosa..." 
          style={{ padding: '10px', fontSize: '1em' }}
        />
      </header>
    </div>
  );
}

export default App;

Spiegazione:

  • const [messaggio, setMessaggio] = useState(...): Dichiaramo una variabile di stato per il messaggio visualizzato.
  • const gestisciClick = () => { ... }: Questa è la funzione che verrà eseguita quando l'evento onClick del bottone si verifica. All'interno, aggiorniamo lo stato messaggio.
  • <button>: Qui, passiamo la funzione gestisciClick come handler per l'evento onClick. Nota bene: non chiamiamo la funzione (gestisciClick()), ma passiamo un riferimento ad essa. React la chiamerà per noi quando l'evento si verifica.
  • const gestisciInput = (event) => { ... }: Questa funzione riceve l'oggetto event come parametro. L'oggetto event contiene informazioni sull'evento, inclusi event.target che si riferisce all'elemento DOM che ha scatenato l'evento (in questo caso, l'input). event.target.value ci dà il valore corrente dell'input.
  • <input />: Per gli input, l'evento onChange si scatena ad ogni modifica del valore dell'input. Questo ci permette di catturare ciò che l'utente digita in tempo reale.

Salva il file e prova l'applicazione. Vedrai che il messaggio cambia sia cliccando il bottone che digitando nel campo di input. La gestione degli eventi è un pilastro dell'interattività in React.

Il Ciclo di Vita dei Componenti e l'Hook useEffect

Ogni componente React attraversa una serie di fasi dalla sua creazione alla sua distruzione. Questo è chiamato il ciclo di vita del componente. Comprendere il ciclo di vita è cruciale per eseguire operazioni specifiche in momenti precisi, come fetching dati da un'API, impostare listener di eventi globali, o pulire risorse quando un componente non è più necessario.

Nei componenti a funzione, l'Hook useEffect è il principale strumento per gestire gli "effetti collaterali" (side effects) che avvengono durante il ciclo di vita del componente. Gli effetti collaterali sono tutte quelle operazioni che interagiscono con il mondo esterno all'applicazione React, come:

  • Chiamate API per fetching dati.
  • Manipolazione diretta del DOM.
  • Impostazione di timer o intervalli (setTimeout, setInterval).
  • Iscrizione a eventi globali o librerie esterne.

L'Hook useEffect in dettaglio

useEffect accetta due argomenti:

  1. Una funzione che contiene la logica dell'effetto collaterale.
  2. Un array di dipendenze (opzionale).
useEffect(() => {
  // La tua logica dell'effetto collaterale
  // Questa funzione viene eseguita dopo ogni render del componente per impostazione predefinita

  return () => {
    // Funzione di cleanup (opzionale)
    // Questa viene eseguita prima che l'effetto venga eseguito di nuovo o quando il componente viene smontato
  };
}, [dipendenza1, dipendenza2]); // Array di dipendenze (opzionale)

Vediamo i diversi scenari basati sull'array di dipendenze:

  1. Nessun array di dipendenze: useEffect(() => { /* ... */ });

    • L'effetto viene eseguito dopo ogni render del componente. Usalo con cautela, poiché può portare a loop infiniti se non gestito correttamente (es. aggiornare lo stato all'interno dell'effetto senza condizioni).
  2. Array di dipendenze vuoto: useEffect(() => { /* ... */ }, []);

    • L'effetto viene eseguito solo una volta dopo il primo render (montaggio del componente). È l'equivalente del metodo componentDidMount nei componenti a classe. Ideale per fetching dati iniziali o per impostare listener globali.
  3. Array di dipendenze con valori: useEffect(() => { /* ... */ }, [prop1, state2]);

    • L'effetto viene eseguito dopo il primo render e ogni volta che una delle dipendenze nell'array cambia. Questo è l'equivalente di componentDidMount + componentDidUpdate (per quelle dipendenze specifiche). React confronta i valori delle dipendenze tra un render e l'altro. Se un valore è diverso, l'effetto viene rieseguito.

La Funzione di Cleanup

Spesso, quando un effetto imposta qualcosa (come un timer, un listener, o una sottoscrizione), è necessario "pulire" quella risorsa quando il componente non è più necessario o quando l'effetto deve essere rieseguito con nuove dipendenze. Puoi farlo restituendo una funzione dalla funzione dell'effetto.

Questa funzione di cleanup viene eseguita:

  • Prima che l'effetto venga rieseguito (se le dipendenze cambiano).
  • Quando il componente viene smontato dal DOM (equivalente a componentWillUnmount).

Esempio di useEffect con fetching dati (simulato):

Creiamo un componente FetchDati che simula il recupero di dati da un'API.

// src/FetchDati.js (nuovo file)
import React, { useState, useEffect } from 'react';

function FetchDati() {
  const [dati, setDati] = useState(null);
  const [caricamento, setCaricamento] = useState(true);
  const [errore, setErrore] = useState(null);

  // L'effetto verrà eseguito solo una volta al montaggio del componente (grazie all'array vuoto [])
  useEffect(() => {
    console.log('Fetching dati...');
    // Simuliamo una chiamata API con un ritardo
    const timer = setTimeout(() => {
      fetch('https://jsonplaceholder.typicode.com/todos/1') // Esempio di API pubblica
        .then(response => {
          if (!response.ok) {
            throw new Error(`HTTP error! status: ${response.status}`);
          }
          return response.json();
        })
        .then(data => {
          setDati(data);
          setCaricamento(false);
        })
        .catch(error => {
          setErrore(error);
          setCaricamento(false);
        });
    }, 2000); // Ritardo di 2 secondi

    // Funzione di cleanup: viene eseguita se il componente viene smontato
    // o se l'effetto dovesse rieseguire (non in questo caso con [])
    return () => {
      console.log('Cleanup: clearing timeout');
      clearTimeout(timer);
    };
  }, []); // Array di dipendenze vuoto: esegui solo una volta al montaggio

  if (caricamento) {
    return <p>Caricamento dati...</p>;
  }

  if (errore) {
    return <p>Errore durante il caricamento: {errore.message}</p>;
  }

  return (
    <div>
      <h3>Dati Caricati:</h3>
      <p>Titolo: {dati.title}</p>
      <p>Completato: {dati.completed ? 'Sì' : 'No'}</p>
    </div>
  );
}

export default FetchDati;

Integra FetchDati in App.js:

// src/App.js (modificato)
import React from 'react';
import './App.css';
import FetchDati from './FetchDati'; // Importiamo il componente FetchDati

function App() {
  return (
    <div className="App">
      <header className="App-header">
        <h1>Il tuo Componente con Fetching Dati</h1>
        <FetchDati />
      </header>
    </div>
  );
}

export default App;

Salva e osserva. Dopo circa due secondi, i dati verranno caricati e visualizzati. Se rimuovi il componente FetchDati da App.js mentre è in fase di caricamento (simulando uno smontaggio), vedrai il messaggio di cleanup nella console.

useEffect è uno degli Hooks più potenti e versatili, ma richiede pratica per essere padroneggiato. È fondamentale capire l'importanza dell'array di dipendenze per evitare problemi di performance o bug legati a effetti non desiderati.

Esempi Pratici: Costruire una Lista di Cose da Fare (Todo List)

Ora che abbiamo coperto i concetti fondamentali – componenti, JSX, props, state, eventi e useEffect – è il momento di mettere tutto insieme per costruire una piccola ma completa applicazione: una Lista di Cose da Fare (Todo List). Questo è un progetto classico per i principianti e ti aiuterà a consolidare le tue conoscenze.

Requisiti della nostra Todo List

La nostra Todo List avrà le seguenti funzionalità:

  1. Visualizzare una lista di attività (todos).
  2. Aggiungere nuove attività.
  3. Segnare un'attività come completata/non completata.
  4. Eliminare un'attività.

Struttura dei Componenti

Suddivideremo l'applicazione in tre componenti principali:

  • App.js: Il componente padre che gestirà lo stato globale dell'intera lista di attività e le funzioni per modificarla.
  • TodoForm.js: Un componente per l'input e il bottone di aggiunta di nuove attività.
  • TodoList.js: Un componente che visualizzerà la lista di attività e passerà le singole attività al componente TodoItem.
  • TodoItem.js: Un singolo componente per ogni attività, che mostrerà il testo, un bottone per completare e un bottone per eliminare.

Implementazione Passo-Passo

1. App.js: Il componente principale e gestore dello stato

Iniziamo con App.js. Qui terremo lo stato di tutte le attività. Useremo useState per gestire l'array todos.

// src/App.js
import React, { useState } from 'react';
import './App.css'; // Puoi rimuovere o adattare il CSS predefinito di CRA
import TodoForm from './TodoForm';
import TodoList from './TodoList';

function App() {
  const [todos, setTodos] = useState([]); // Array di oggetti todo

  // Funzione per aggiungere un nuovo todo
  const addTodo = (text) => {
    const newTodo = {
      id: Date.now(), // ID unico basato sul timestamp
      text: text,
      completed: false,
    };
    setTodos([...todos, newTodo]); // Aggiungiamo il nuovo todo all'array esistente
  };

  // Funzione per segnare un todo come completato/non completato
  const toggleComplete = (id) => {
    setTodos(todos.map(todo => 
      todo.id === id ? { ...todo, completed: !todo.completed } : todo
    ));
  };

  // Funzione per eliminare un todo
  const deleteTodo = (id) => {
    setTodos(todos.filter(todo => todo.id !== id));
  };

  return (
    <div className="App">
      <header className="App-header">
        <h1>La Mia Todo List</h1>
        <TodoForm addTodo={addTodo} /> {/* Passiamo la funzione addTodo come prop */}
        <TodoList 
          todos={todos} 
          toggleComplete={toggleComplete} 
          deleteTodo={deleteTodo} 
        /> {/* Passiamo l'array di todos e le funzioni come props */}
      </header>
    </div>
  );
}

export default App;

Spiegazione di App.js:

  • const [todos, setTodos] = useState([]);: Inizializziamo lo stato todos come un array vuoto. Ogni todo sarà un oggetto { id, text, completed }.
  • addTodo, toggleComplete, deleteTodo: Queste funzioni sono definite in App perché è qui che risiede lo stato todos. Modificano lo stato utilizzando setTodos.
    • addTodo: Crea un nuovo oggetto todo con un ID unico e lo aggiunge all'array todos usando l'operatore spread ... per creare un nuovo array (ricorda, non modificare lo stato direttamente).
    • toggleComplete: Mappa l'array todos e, se trova l'ID corrispondente, crea un nuovo oggetto todo con la proprietà completed invertita. Altrimenti, restituisce il todo originale. Anche qui, creiamo un nuovo array.
    • deleteTodo: Filtra l'array todos, rimuovendo l'attività con l'ID specificato.
  • Passiamo queste funzioni e l'array todos ai componenti figli come props.

2. TodoForm.js: Aggiungere nuove attività

Questo componente avrà un campo di input e un bottone per aggiungere nuove attività. Riceverà la funzione addTodo come prop dal componente App.

// src/TodoForm.js
import React, { useState } from 'react';

function TodoForm({ addTodo }) { // Destructuring per accedere direttamente a 'addTodo'
  const [inputValue, setInputValue] = useState('');

  const handleSubmit = (event) => {
    event.preventDefault(); // Previene il ricaricamento della pagina al submit del form
    if (!inputValue.trim()) return; // Non aggiungere todo vuoti
    addTodo(inputValue); // Chiamiamo la funzione passata come prop
    setInputValue(''); // Resettiamo l'input dopo l'aggiunta
  };

  return (
    <form style={{ marginBottom: '20px' }}>
      <input
        type="text"
        placeholder="Aggiungi una nuova attività..."
        value={inputValue} => setInputValue(e.target.value)}
        style={{ padding: '10px', fontSize: '1em', marginRight: '10px', borderRadius: '5px', border: '1px solid #ddd' }}
      />
      <button 
        type="submit" 
        style={{ padding: '10px 15px', fontSize: '1em', backgroundColor: '#61dafb', color: 'white', border: 'none', borderRadius: '5px', cursor: 'pointer' }}
      >
        Aggiungi
      </button>
    </form>
  );
}

export default TodoForm;

3. TodoList.js: Visualizzare la lista

Questo componente riceverà l'array todos e le funzioni di callback. Renderizzerà un componente TodoItem per ogni attività nell'array.

// src/TodoList.js
import React from 'react';
import TodoItem from './TodoItem';

function TodoList({ todos, toggleComplete, deleteTodo }) {
  return (
    <div style={{ width: '100%', maxWidth: '500px' }}>
      {todos.length === 0 ? (
        <p>Nessuna attività. Aggiungine una!</p>
      ) : (
        <ul style={{ listStyle: 'none', padding: 0 }}>
          {todos.map(todo => (
            <TodoItem 
              key={todo.id} // La prop 'key' è fondamentale per le liste in React!
              todo={todo} 
              toggleComplete={toggleComplete} 
              deleteTodo={deleteTodo} 
            />
          ))}
        </ul>
      )}
    </div>
  );
}

export default TodoList;

Spiegazione di TodoList.js:

  • todos.map(...): Il metodo map è usato per iterare su ogni elemento dell'array todos e restituire un componente TodoItem per ciascuno.
  • key={todo.id}: Questa è una prop speciale e obbligatoria quando si renderizzano liste di elementi in React. React usa la key per identificare in modo univoco ogni elemento della lista e ottimizzare il processo di aggiornamento del DOM. La key dovrebbe essere un valore stabile e unico per ogni elemento (l'ID del todo è perfetto).

4. TodoItem.js: Il singolo elemento della lista

Questo componente riceverà un singolo oggetto todo e le funzioni toggleComplete e deleteTodo. Sarà responsabile di visualizzare il testo dell'attività, il suo stato di completamento e i bottoni per le azioni.

// src/TodoItem.js
import React from 'react';

function TodoItem({ todo, toggleComplete, deleteTodo }) {
  const itemStyle = {
    display: 'flex',
    justifyContent: 'space-between',
    alignItems: 'center',
    padding: '10px',
    marginBottom: '8px',
    backgroundColor: todo.completed ? '#d4edda' : '#f8f9fa', // Colore diverso se completato
    border: `1px solid ${todo.completed ? '#28a745' : '#ccc'}`, // Bordo diverso
    borderRadius: '5px',
    textDecoration: todo.completed ? 'line-through' : 'none',
    color: todo.completed ? '#6c757d' : '#343a40'
  };

  const buttonStyle = {
    padding: '8px 12px',
    fontSize: '0.8em',
    border: 'none',
    borderRadius: '4px',
    cursor: 'pointer',
    marginLeft: '5px'
  };

  return (
    <li style={itemStyle}>
      <span => toggleComplete(todo.id)} style={{ cursor: 'pointer', flexGrow: 1 }}>
        {todo.text}
      </span>
      <div>
        <button => toggleComplete(todo.id)}
          style={{ ...buttonStyle, backgroundColor: todo.completed ? '#dc3545' : '#28a745', color: 'white' }}
        >
          {todo.completed ? 'Annulla' : 'Completa'}
        </button>
        <button => deleteTodo(todo.id)}
          style={{ ...buttonStyle, backgroundColor: '#ffc107', color: '#333' }}
        >
          Elimina
        </button>
      </div>
    </li>
  );
}

export default TodoItem;

Salva tutti i file e avvia l'applicazione (npm start). Avrai ora una Todo List completamente funzionante, costruita pezzo per pezzo con i concetti che abbiamo imparato! Questo esempio dimostra come i componenti comunicano tramite props e come lo stato gestisce i dati dinamici.

Errori Comuni per i Principianti e Come Risolverli

Iniziare con React può essere entusiasmante, ma è normale incontrare alcuni ostacoli. Conoscere gli errori più comuni ti aiuterà a identificarli e risolverli più rapidamente.

  1. Dimenticare la key nelle liste: Questo è forse l'errore più comune. Quando renderizzi una lista di elementi (ad esempio, con map), React ti avviserà con un warning nella console: Warning: Each child in a list should have a unique "key" prop.. La key aiuta React a identificare quali elementi sono cambiati, aggiunti o rimossi in modo efficiente. Usa sempre un ID unico e stabile per la key.

    • Soluzione: Assicurati che ogni elemento figlio diretto di una lista (es. <li key={item.id}>) abbia una prop key univoca.
  2. Modificare lo stato direttamente: Un errore frequente è tentare di modificare una variabile di stato come una normale variabile JavaScript, ad esempio todos.push(newTodo) o conteggio = conteggio + 1;. Questo non attiverà un re-render del componente e la UI non si aggiornerà.

    • Soluzione: Usa sempre la funzione di aggiornamento fornita da useState (es. setTodos([...todos, newTodo]), setConteggio(conteggio + 1)). Per gli oggetti e gli array, assicurati di creare una nuova copia.
  3. Loop infiniti con useEffect: Se un useEffect aggiorna lo stato senza un array di dipendenze (o con un array di dipendenze che include lo stato che viene aggiornato), può causare un loop infinito dove l'effetto aggiorna lo stato, che causa un re-render, che riesegue l'effetto, e così via.

    • Soluzione: Controlla attentamente l'array di dipendenze del tuo useEffect. Se l'effetto deve essere eseguito solo una volta al montaggio, usa []. Se dipende da alcune variabili, includile nell'array. Se non dipende da nulla e non aggiorna lo stato, potresti non aver bisogno di useEffect.
  4. Cannot read properties of undefined (o simili): Questo errore si verifica spesso quando tenti di accedere a una proprietà di un oggetto che è undefined o null. Questo può accadere se le props non vengono passate correttamente, se lo stato non è ancora stato inizializzato con i dati attesi (ad esempio, durante un fetching asincrono), o se c'è un errore di battitura nel nome di una prop o di una variabile di stato.

    • Soluzione: Controlla l'origine dei dati (props o state). Assicurati che siano definiti prima di tentare di accedervi. Durante il fetching dati, mostra uno stato di caricamento (if (loading) return <p>Caricamento...</p>;). Usa l'optional chaining (dati?.proprieta) o il rendering condizionale.
  5. Non chiudere i tag JSX o non avere un singolo elemento root: JSX richiede che tutti i tag siano chiusi (es. <img />, <br />) e che ogni componente restituisca un singolo elemento padre (o un Fragment).

    • Soluzione: Aggiungi lo slash finale ai tag auto-chiudenti. Avvolgi più elementi adiacenti in un <div> o in un <React.Fragment> (o la sua forma abbreviata <></>).
  6. Confusione tra JSX e HTML/JavaScript: Ricorda che JSX ha alcune differenze rispetto all'HTML (es. className invece di class, htmlFor invece di for) e che le espressioni JavaScript devono essere racchiuse tra parentesi graffe {}.

    • Soluzione: Fai attenzione alle differenze di sintassi e consulta la documentazione di React se hai dubbi.

Sviluppare con React significa imparare a leggere i messaggi di errore nella console del browser. Sono i tuoi migliori amici per il debugging!

Prossimi Passi nel Tuo Percorso React

Congratulazioni! Hai completato questa guida introduttiva a React e hai costruito la tua prima applicazione interattiva. Questo è un ottimo punto di partenza, ma il mondo di React è vasto e offre molte altre opportunità di apprendimento. Ecco alcuni prossimi passi e aree in cui potresti voler approfondire:

  1. Routing con React Router DOM: Le applicazioni reali hanno più pagine. React Router DOM è la libreria standard per gestire la navigazione tra diverse "pagine" (componenti) nella tua Single Page Application (SPA).
  2. Gestione dello Stato Avanzata: Per applicazioni più grandi, lo stato può diventare complesso. Esplora:
    • Context API: Uno strumento integrato in React per passare dati attraverso l'albero dei componenti senza dover passare manualmente le props a ogni livello (prop drilling).
    • Redux / Zustand / Jotai: Librerie esterne per una gestione dello stato più robusta e scalabile, ideali per applicazioni complesse con stato globale distribuito.
  3. Fetching Dati Reali: Abbiamo simulato una chiamata API con fetch. Approfondisci:
    • Axios: Una popolare libreria HTTP client per fare richieste API in modo più semplice e potente.
    • React Query / SWR: Librerie per la gestione e la cache delle richieste dati, che semplificano notevolmente il fetching, il caching, la sincronizzazione e l'aggiornamento dei dati lato client.
  4. Styling in React: Ci sono molti modi per stilizzare i tuoi componenti:
    • CSS Modules: Incapsula gli stili CSS a livello di componente per evitare conflitti di nomi.
    • Styled Components / Emotion: Librerie che ti permettono di scrivere CSS direttamente nel tuo JavaScript (CSS-in-JS).
    • Tailwind CSS: Un framework CSS utility-first che ti permette di costruire design complessi direttamente nel tuo markup con classi predefinite.
  5. Hooks Avanzati e Custom Hooks: Esplora altri Hooks come useReducer (per gestire stati complessi), useContext (per accedere al Context API), useCallback, useMemo (per ottimizzare le performance) e impara a creare i tuoi Custom Hooks per riutilizzare la logica di stato tra componenti.
  6. Testing: Impara a scrivere test per i tuoi componenti React usando librerie come Jest e React Testing Library, per assicurarti che la tua applicazione funzioni come previsto e per prevenire regressioni.
  7. TypeScript con React: Man mano che i progetti crescono, l'aggiunta di TypeScript può migliorare la manutenibilità, la leggibilità e la robustezza del codice, fornendo type checking statico.
  8. Deployment: Scopri come mettere online la tua applicazione React usando servizi come Vercel, Netlify o GitHub Pages.

React è una libreria potente e in continua evoluzione. Continua a praticare, a costruire progetti e a esplorare la documentazione ufficiale. La chiave per diventare uno sviluppatore React esperto è la pratica costante e la curiosità di imparare sempre cose nuove. In bocca al lupo per il tuo percorso nel mondo di React!