React ti permette di aggiungere dei gestori di eventi al tuo JSX. I gestori di eventi sono funzioni scritte da te che verranno attivate in risposta ad interazioni come il click, il passaggio del mouse, il focus sugli input dei form, e così via.

Imparerai

  • Modi differenti per scrivere un gestore di eventi
  • Come passare la logica di gestione degli eventi da un componente genitore
  • Come si propagano gli eventi e come fermarli

Aggiungere gestori di eventi

Per aggiungere un gestore di eventi, dovrai prima definire una funzione e poi passarla come prop al tag JSX appropriato. Ad esempio, ecco un bottone che per il momento non fa nulla:

export default function Button() {
  return (
    <button>
      I don't do anything
    </button>
  );
}

Puoi fargli mostrare un messaggio quando un utente clicca seguendo questi tre passaggi:

  1. Dichiara una funzione chiamata handleClick all’interno del tuo componente Button.
  2. Implementa la logica all’interno di quella funzione (usa alert per mostrare il messaggio).
  3. Aggiungi onClick={handleClick} al tuo JSX <button>.
export default function Button() {
  function handleClick() {
    alert('You clicked me!');
  }

  return (
    <button onClick={handleClick}>
      Click me
    </button>
  );
}

Hai definito la funzione handleClick e poi l’hai passata come prop al tag <button>. handleClick è un gestore di eventi. Le funzioni gestore di eventi:

  • Sono solitamente definite all’interno dei tuoi componenti.
  • Hanno nomi che iniziano con handle, seguiti dal nome dell’evento.

Per convenzione, è comune chiamare i gestori di eventi come handle seguito dal nome dell’evento. Vedrai spesso onClick={handleClick}, onMouseEnter={handleMouseEnter}, e così via.

In alternativa, puoi definire un gestore di eventi inline nel JSX:

<button onClick={function handleClick() {
alert('You clicked me!');
}}>

Oppure, più sinteticamente, usando una arrow function:

<button onClick={() => {
alert('You clicked me!');
}}>

Tutti questi stili sono equivalenti. I gestori di eventi inline sono comodi per le funzioni brevi.

Insidia

Le funzioni passate ai gestori di eventi devono essere passate appunto, non chiamate. Ad esempio:

passando una funzione (corretto)chiamando una funzione (incorretto)
<button onClick={handleClick}><button onClick={handleClick()}>

La differenza è minima. Nel primo esempio, la funzione handleClick viene passata come gestore di eventi onClick. Questo dice a React di ricordarla e di chiamarla solo quando l’utente clicca sul bottone.

Nel secondo esempio, le () alla fine di handleClick() fanno eseguire la funzione immediatamente durante il rendering, senza alcun click. Questo perché il codice JavaScript all’interno delle parentesi graffe { e } del JSX viene eseguito immediatamente.

Quando scrivi codice inline, la stessa insidia si presenta in modo diverso:

passando una funzione (corretto)chiamando una funzione (incorretto)
<button onClick={() => alert('...')}><button onClick={alert('...')}>

Passando il codice inline in questo modo non scatena l’evento al click — viene scatenato ogni volta che il componente viene renderizzato:

// Questo alert viene scatenato quando il componente viene renderizzato, non quando viene cliccato!
<button onClick={alert('You clicked me!')}>

Se vuoi definire il tuo gestore di eventi inline, incapsulalo in una funzione anonima come segue:

<button onClick={() => alert('You clicked me!')}>

Invece di eseguire il codice all’interno con ogni render, questo crea una funzione da chiamare in seguito.

In entrambi i casi, quello che vuoi passare è una funzione:

  • <button onClick={handleClick}> passa la funzione handleClick.
  • <button onClick={() => alert('...')}> passa la funzione () => alert('...').

Leggi di più in merito alle arrow functions.

Leggere le props nei gestori di eventi

Poichè i gestori di eventi sono dichiarati all’interno di un componente, questi hanno accesso alle props del componente. Ecco un bottone che, quando cliccato, mostra un alert con la prop message:

function AlertButton({ message, children }) {
  return (
    <button onClick={() => alert(message)}>
      {children}
    </button>
  );
}

export default function Toolbar() {
  return (
    <div>
      <AlertButton message="Playing!">
        Play Movie
      </AlertButton>
      <AlertButton message="Uploading!">
        Upload Image
      </AlertButton>
    </div>
  );
}

Questo permette a questi due bottoni di mostrare messaggi differenti. Prova a cambiare i messaggi che gli vengono passati.

Passare i gestori di eventi come props

Spesso vorrai che il componente genitore specifichi il gestore di eventi di un componente figlio. Considera i bottoni: a seconda di dove stai usando un componente Button, potresti voler eseguire una funzione diversa — forse una riproduce un film ed un’altra carica un’immagine.

Per fare questo, passa una prop che il componente riceve dal suo componente genitore come gestore di eventi in questo modo:

function Button({ onClick, children }) {
  return (
    <button onClick={onClick}>
      {children}
    </button>
  );
}

function PlayButton({ movieName }) {
  function handlePlayClick() {
    alert(`Playing ${movieName}!`);
  }

  return (
    <Button onClick={handlePlayClick}>
      Play "{movieName}"
    </Button>
  );
}

function UploadButton() {
  return (
    <Button onClick={() => alert('Uploading!')}>
      Upload Image
    </Button>
  );
}

export default function Toolbar() {
  return (
    <div>
      <PlayButton movieName="Kiki's Delivery Service" />
      <UploadButton />
    </div>
  );
}

Qui, il componente Toolbar renderizza un PlayButton ed un UploadButton:

  • PlayButton passa handlePlayClick come prop onClick al Button interno.
  • UploadButton passa () => alert('Uploading!') come prop onClick al Button interno.

Infine, il componente Button accetta una prop chiamata onClick. Passa questa prop direttamente al <button> del browser con onClick={onClick}. Questo dice a React di chiamare al click la funzione passata.

Se utilizzi un design system, è comune che i componenti come i bottoni contengano lo stile ma non specificano il comportamento. Invece, i componenti come PlayButton e UploadButton passeranno i gestori di eventi in basso.

Denominare le props dei gestori di eventi

I componenti integrati come <button> e <div> supportano solo i nomi degli eventi del browser come onClick. Tuttavia, quando stai costruendo i tuoi componenti, puoi chiamare le props dei loro gestori di eventi in qualsiasi modo ti piaccia.

Per convenzione, le props dei gestori di eventi dovrebbero iniziare con on, seguito da una lettera maiuscola.

Ad esempio, la prop onClick del componente Button avrebbe potuto essere chiamata onSmash:

function Button({ onSmash, children }) {
  return (
    <button onClick={onSmash}>
      {children}
    </button>
  );
}

export default function App() {
  return (
    <div>
      <Button onSmash={() => alert('Playing!')}>
        Play Movie
      </Button>
      <Button onSmash={() => alert('Uploading!')}>
        Upload Image
      </Button>
    </div>
  );
}

In questo esempio, <button onClick={onSmash}> mostra che il <button> del browser (in minuscolo) necessita di una prop chiamata onClick, ma il nome della prop ricevuta dal tuo componente Button custom puoi deciderlo tu!

Quando il tuo componente supporta molteplici interazioni, potresti chiamare le props dei gestori di eventi con concetti specifici all’applicazione. Ad esempio, questo componente Toolbar riceve i gestori di eventi onPlayMovie e onUploadImage:

export default function App() {
  return (
    <Toolbar
      onPlayMovie={() => alert('Playing!')}
      onUploadImage={() => alert('Uploading!')}
    />
  );
}

function Toolbar({ onPlayMovie, onUploadImage }) {
  return (
    <div>
      <Button onClick={onPlayMovie}>
        Play Movie
      </Button>
      <Button onClick={onUploadImage}>
        Upload Image
      </Button>
    </div>
  );
}

function Button({ onClick, children }) {
  return (
    <button onClick={onClick}>
      {children}
    </button>
  );
}

Nota come il componente App non ha bisogno di sapere cosa farà Toolbar con onPlayMovie o onUploadImage. Questo è un dettaglio di implementazione di Toolbar. Qui, Toolbar li passa come gestori di eventi onClick ai suoi Button, ma potrebbe anche attivarli con una scorciatoia da tastiera. Dare alle props nomi specifici relativi all’applicazione come onPlayMovie ti dà la flessibilità di cambiare come vengono utilizzate in seguito.

Nota bene

Assicurati di utilizzare i tag HTML appropriati per i tuoi gestori di eventi. Ad esempio, per gestire i click, usa <button onClick={handleClick}> invece di <div onClick={handleClick}>. L’uso di un vero pulsante del browser <button> consente comportamenti del browser integrati come la navigazione da tastiera. Se non ti piace lo stile predefinito del browser di un pulsante e vuoi farlo sembrare più un link o un diverso elemento dell’interfaccia utente, puoi ottenerlo con CSS. Scopri di più sulla scrittura di markup accessibile.

Propagazione degli eventi

I gestori di eventi cattureranno gli eventi da ogni elemento figlio che il tuo componente può possedere. Diciamo che un evento “bolle” o “si propaga” verso l’alto nell’albero: inizia con dove è avvenuto l’evento, e poi sale nell’albero.

Questo <div> contiene due bottoni. Sia il <div> ed ogni bottone hanno i propri gestori onClick. Quali gestori pensi che scatteranno quando cliccherai un bottone?

export default function Toolbar() {
  return (
    <div className="Toolbar" onClick={() => {
      alert('You clicked on the toolbar!');
    }}>
      <button onClick={() => alert('Playing!')}>
        Play Movie
      </button>
      <button onClick={() => alert('Uploading!')}>
        Upload Image
      </button>
    </div>
  );
}

Se clicchi su entrambi i bottoni, i loro onClick scatteranno per primi, seguiti dall’onClick del <div> genitore. Appariranno quindi due messaggi. Se clicchi sulla toolbar, scatterà soltanto l’onClick del <div> genitore.

Insidia

In React, tutti gli eventi si propagano eccetto onScroll, che funziona soltanto sul tag JSX al quale lo attacchi.

Stoppare la propagazione

Tutti i gestori di eventi ricevono un oggetto evento come loro unico argomento. Per convenzione, viene solitamente chiamato e, che sta per “evento”. Puoi usare questo oggetto per leggere informazioni sull’evento.

Questo oggetto evento ti permette anche di stoppare la propagazione. Se vuoi prevenire che un evento raggiunga i componenti genitori, devi chiamare e.stopPropagation() come fa questo componente Button:

function Button({ onClick, children }) {
  return (
    <button onClick={e => {
      e.stopPropagation();
      onClick();
    }}>
      {children}
    </button>
  );
}

export default function Toolbar() {
  return (
    <div className="Toolbar" onClick={() => {
      alert('You clicked on the toolbar!');
    }}>
      <Button onClick={() => alert('Playing!')}>
        Play Movie
      </Button>
      <Button onClick={() => alert('Uploading!')}>
        Upload Image
      </Button>
    </div>
  );
}

Quando clicchi su un bottone:

  1. React chiama il gestore onClick passato al <button>.
  2. Quel gestore, definito in Button, fa quanto segue:
    • Chiama e.stopPropagation(), prevenendo l’ulteriore propagazione dell’evento.
    • Chiama la funzione onClick, che è una prop passata dal componente Toolbar.
  3. Quella funzione, definita nel componente Toolbar, mostra l’alert del bottone stesso.
  4. Visto che la propagazione è stata fermata, il gestore onClick del genitore <div> non viene eseguito.

Come risultato di e.stopPropagation(), cliccando sui bottoni adesso mostra soltanto un singolo alert (dal <button>) piuttosto che due (dal <button> e dal <div> genitore della toolbar). Cliccare un bottone non è la stessa cosa che cliccare la toolbar circostante, quindi fermare la propagazione ha senso per questa UI.

Approfondimento

Catturare eventi di fase

In rari casi, potresti aver bisogno di catturare tutti gli eventi sugli elementi figli, anche se la propagazione è stata fermata. Per esempio, potresti voler loggare ogni click per gli analytics, indipendentemente dalla logica di propagazione. Puoi farlo aggiungendo Capture al termine del nome dell’evento:

<div onClickCapture={() => { /* questo scatta per primo */ }}>
<button onClick={e => e.stopPropagation()} />
<button onClick={e => e.stopPropagation()} />
</div>

Ogni evento si propaga in tre fasi:

  1. Viaggia verso il basso, chiamando tutti i gestori onClickCapture.
  2. Esegue il gestore onClick dell’elemento cliccato.
  3. Viaggia verso l’alto, chiamando tutti i gestori onClick.

Catturare gli eventi è utile per codice come router o analytics, ma probabilmente non li userai nel codice dell’app.

Passare gestori come alternativa alla propagazione

Nota come questo gestore di click esegue una riga di codice e poi chiama la prop onClick passata dal genitore:

function Button({ onClick, children }) {
return (
<button onClick={e => {
e.stopPropagation();
onClick();
}}>
{children}
</button>
);
}

Potresti aggiungere più codice a questo gestore prima di chiamare il gestore di eventi onClick del genitore. Questo pattern fornisce un’alternativa alla propagazione. Permette al componente figlio di gestire l’evento, mentre permette anche al componente genitore di specificare qualche comportamento aggiuntivo. A differenza della propagazione, non è automatico. Ma il vantaggio di questo pattern è che puoi seguire chiaramente tutta la catena di codice che si esegue come risultato di qualche evento.

Se ti affidi alla propagazione e ti risulta difficile tracciare quali gestori vengono eseguiti e perché, prova questo approccio.

Prevenire comportamenti di default

Alcuni eventi del browser hanno un comportamento di default associato ad essi. Per esempio, un evento di submit di un <form>, che si verifica quando viene cliccato un bottone al suo interno, ricaricherà la pagina di default:

export default function Signup() {
  return (
    <form onSubmit={() => alert('Submitting!')}>
      <input />
      <button>Send</button>
    </form>
  );
}

Puoi chiamare e.preventDefault() sull’oggetto dell’evento per fermare questo comportamento:

export default function Signup() {
  return (
    <form onSubmit={e => {
      e.preventDefault();
      alert('Submitting!');
    }}>
      <input />
      <button>Send</button>
    </form>
  );
}

Non confondere e.stopPropagation() e e.preventDefault(). Sono entrambi utili, ma non sono correlati:

  • e.stopPropagation() interrompe l’attivazione dei gestori di eventi associati ai tag soprastanti.
  • e.preventDefault() impedisce il comportamento di default del browser per i pochi eventi che lo hanno.

I gestori di eventi possono avere effetti collaterali?

Assolutamente! I gestori di eventi sono il posto migliore per gli effetti collaterali.

A differenza delle funzioni di rendering, i gestori di eventi non devono essere puri, quindi è un ottimo posto per cambiare qualcosa, per esempio cambiare il valore di un input in risposta alla digitazione o cambiare una lista in risposta alla pressione di un bottone. Tuttavia, per cambiare qualche informazione, devi prima avere un modo per memorizzarla. In React, questo viene fatto utilizzando lo state, la memoria di un componente. Imparerai tutto su di esso nella prossima pagina.

Riepilogo

  • Puoi gestire gli eventi passando una funzione come prop ad un elemento come <button>.
  • I gestori di eventi devono essere passati, non chiamati!. onClick={handleClick}, non onClick={handleClick()}.
  • Puoi definire una funzione di gestione degli eventi separatamente oppure inline.
  • I gestori di eventi sono definiti all’interno di un componente, quindi possono accedere alle props.
  • Puoi dichiarare un gestore di eventi in un genitore e passarlo come prop ad un figlio.
  • Puoi definire le tue props di gestione degli eventi con nomi specifici dell’applicazione.
  • Gli eventi si propagano verso l’alto. Chiama e.stopPropagation() sul primo argomento per impedirlo.
  • Gli eventi possono avere alcuni comportamenti indesiderati di default del browser. Chiama e.preventDefault() per impedirlo.
  • Chiamare esplicitamente una prop di gestione degli eventi da un gestore figlio è un’ottima alternativa alla propagazione.

Sfida 1 di 2:
Aggiustare un gestore di eventi

Cliccando questo bottone si suppone che lo sfondo della pagina passi da bianco a nero. Tuttavia, non succede nulla quando lo clicchi. Risolvi il problema. (Non preoccuparti della logica all’interno di handleClick - quella parte va bene)

export default function LightSwitch() {
  function handleClick() {
    let bodyStyle = document.body.style;
    if (bodyStyle.backgroundColor === 'black') {
      bodyStyle.backgroundColor = 'white';
    } else {
      bodyStyle.backgroundColor = 'black';
    }
  }

  return (
    <button onClick={handleClick()}>
      Toggle the lights
    </button>
  );
}