CONTATTACI

Guide per aspiranti programmatori

Programmatore che esamina react al microscopio
Lezione 0 / 41

Esempio pratico: gestione dell’input di un utente in React

Per consolidare la nostra conoscenza di props, eventi e stati, impostiamo un form per la creazione di un elemento di una lista di cose da fare (un altro classico esempio in React, ma non solo). Espanderemo questo esempio nelle prossime sezioni.

Ragioniamo un attimo su quello che ci servirà. Un elemento di una lista di cose da fare è definito da due proprietà: la descrizione della cosa da fare, e il fatto che la cosa sia o non sia stata fatta. Di conseguenza, saranno due anche le cose che possono cambiare: possiamo modificare la descrizione dell’elemento, oppure possiamo notificare che l’abbiamo fatto o che non l’abbiamo fatto.

È sempre molto utile decidere in anticipo anche il tipo di dato delle informazioni. Lo stato che rappresenta l’elemento della lista di cose da fare sarà un oggetto con due proprietà:

 

  1. description, che rappresenta la descrizione dell’elemento e sarà una stringa.
  2. isDone, che rappresenta il fatto che la cosa sia o non sia stata fatta e sarà un valore booleano (vero/falso).

 

Quando dobbiamo creare un nuovo componente, un metodo che possiamo utilizzare è creare i due file .jsx e .css, importare il file CSS e far restituire null al componente, per poi inserirlo nel suo componente genitore.
In questo modo, quando cominciamo a modificare il nuovo componente, possiamo già vederne l’aspetto nel browser.

Cominciamo, allora, creando un nuovo componente TodoItemForm, con relativo file CSS, e usando App per ospitarlo:

 

// File: src/components/TodoItemForm.jsx

import "./TodoItemForm.css";

export default function TodoItemForm() {
  return null;
}
// File: src/App.jsx

import "./App.css";
import Panel from "./components/Panel";
import TodoItemForm from "./components/TodoItemForm";

export default function App() {
  return (
    <div className="App">
      <Panel>
        <TodoItemForm />
      </Panel>
    </div>
  );
}

A questo punto, possiamo concentrarci su TodoItemForm:

 

// File: src/components/TodoItemForm.jsx

import { useState } from "react";
import "./TodoItemForm.css";

export default function TodoItemForm() {
  const [todoItem, setTodoItem] = useState({
    description: "",
    isDone: false,
  });

  const onDescriptionChange = (description) => {
    setTodoItem((todoItem) => {
      return {
        ...todoItem,
        description,
      };
    });
  };

  const onIsDoneChange = (isDone) => {
    setTodoItem((todoItem) => {
      return {
        ...todoItem,
        isDone,
      };
    });
  };

  const onSubmit = () => {
    console.log("TODO: do something with this!", todoItem);
  };

  return (
    <form
      className="TodoItemForm"
      onSubmit={(event) => {
        event.preventDefault();
        onSubmit();
      }}
    >
      <input
        type="checkbox"
        checked={todoItem.isDone}
        onChange={(event) => {
          onIsDoneChange(event.currentTarget.checked);
        }}
      />
      <input
        type="text"
        placeholder="Description"
        value={todoItem.description}
        onInput={(event) => {
          onDescriptionChange(event.currentTarget.value);
        }}
      />
      <input type="submit" value="Create" />
    </form>
  );
}

Analizziamo quello che sta succedendo:

 

  • Definiamo lo stato dell’elemento, come un oggetto con le proprietà description (inizializzata con una stringa vuota) e isDone (inizializzata con false).

 

Perché non usare due stati, uno per description e uno per isDone? In questo caso specifico non cambierebbe nulla, nè da un punto di vista di complessità né di performance, ma è bene abituarsi a usare gli stati per rappresentare la realtà. La descrizione da sola non basterebbe a descrivere l’elemento, né basterebbe isDone. È, quindi, corretto che entrambi facciano parte di un solo stato, che rappresenta tutte e sole le informazioni che descrivono l’elemento.

 

  • Definiamo i due eventi, onDescriptionChange (quando cambia la descrizione) e onIsDoneChange (quando cambia isDone). In entrambi i casi, aggiorniamo lo stato con le informazioni più recenti, causando un nuovo ciclo di rendering che aggiornerà l’interfaccia. Visto che gli stati in React sono immutabili, utilizziamo lo spread operator (…) per clonare lo stato di “adesso”, aggiungendoci il valore della proprietà che è stata aggiornata (description in onDescriptionChange, isDone in onIsDoneChange).

 

  • Definiamo l’evento onSubmit, che succederà quando verrà cliccato il bottone che dice “Create”, o alla pressione del tasto invio quando l’input di testo è selezionato, o, in generale, quando la modifica è stata completata. In questo caso, non sapendo ancora cosa fare con i dati aggiornati, li inviamo alla console insieme a un messaggio “TODO”.

 

  • Restituiamo l’interfaccia, che si lega a tre eventi nativi HTML: onSubmit di form, a cui reagiamo prontamente chiamando preventDefault() per evitare che la pagina si ricarichi; onChange di input[type=checkbox], che usiamo per passare il nuovo stato della checkbox alla nostra funzione onIsDoneChange; onInput di input[type=text], che usiamo per passare la nuova descrizione alla nostra funzione onDescriptionChange.

 

Notiamo come usiamo currentTarget invece di target quando utilizziamo gli eventi. Questa è la principale differenza tra gli eventi sintetici di React e gli eventi nativi HTML/JavaScript. Attenzione: se ti capitasse di reagire a un evento con un’operazione lunga, per esempio una chiamata di rete in cui è coinvolta una Promise (o async), può essere che currentTarget, alla fine dell’operazione, sia diventato null. È, quindi, buona norma, in questi casi, mettere da parte currentTarget (con un’istruzione come const currentTarget = event.currentTarget;) immediatamente all’inizio della gestione dell’evento, così che il suo valore sia al sicuro. Per esempio:

 

const eventHandlerHandler = (event) => {
  const currentTarget = event.currentTarget;

  window
    .fetch("http://some-example.com")
    .then((response) => {
      return response.json();
    })
    .then((data) => {
      // Qui possiamo usare currentTarget con confidenza,
      // mentre event.currentTarget è probabilmente null
    });
};

Ecco, invece, il file CSS relativo a TodoItemForm:

 

/* File: src/components/TodoItemForm.css */

.TodoItemForm {
  display: flex;
  align-items: center;
}

.TodoItemForm > * + * {
  margin-left: 0.5em;
}

Solleviamo lo stato

Ora che tutte le funzionalità sono pronte, possiamo sollevare lo stato, portandolo in App: impostiamo lo stato in App, passiamo il suo valore a TodoItemForm tramite props, e colleghiamo un evento onChange per far sì che TodoItemForm notifichi App quando le informazioni cambiano. Anche onSubmit viene spostato in App:

 

// File: src/App.jsx

import { useState } from "react";
import "./App.css";
import Panel from "./components/Panel";
import TodoItemForm from "./components/TodoItemForm";

export default function App() {
  const [todoItem, setTodoItem] = useState({
    description: "",
    isDone: false,
  });

  const onTodoItemChange = (todoItem) => {
    setTodoItem(todoItem);
  };

  const onTodoItemFormSubmit = () => {
    setTodoItem({ description: "", isDone: false });
    console.log("TODO: do something with this!", todoItem);
  };

  return (
    <div className="App">
      <Panel>
        <TodoItemForm
          todoItem={todoItem}
          onChange={onTodoItemChange}
          onSubmit={onTodoItemFormSubmit}
        />
      </Panel>
    </div>
  );
}
// File: src/components/TodoItemForm.jsx

import "./TodoItemForm.css";

export default function TodoItemForm({ todoItem, onChange, onSubmit }) {
  const onDescriptionChange = (description) => {
    onChange({
      ...todoItem,
      description,
    });
  };

  const onIsDoneChange = (isDone) => {
    onChange({
      ...todoItem,
      isDone,
    });
  };

  return (
    <form
      className="TodoItemForm"
      onSubmit={(event) => {
        event.preventDefault();
        onSubmit();
      }}
    >
      <input
        type="checkbox"
        checked={todoItem.isDone}
        onChange={(event) => {
          onIsDoneChange(event.currentTarget.checked);
        }}
      />
      <input
        type="text"
        placeholder="Description"
        value={todoItem.description}
        onInput={(event) => {
          onDescriptionChange(event.currentTarget.value);
        }}
      />
      <input type="submit" value="Create" />
    </form>
  );
}

Notiamo di nuovo il cambio della nomenclatura in App: dato che App potrebbe interagire con altri componenti figli, è bene dare allo stato e agli event listener dei nomi che ci facciano capire di cosa stiamo parlando. In particolare, onSubmit diventa onTodoItemFormSubmit che, contenendo il nome del componente nel nome della funzione, non lascia spazio a interpretazione.

Il sollevamento di stato che abbiamo appena effettuato potrebbe non essere la soluzione ottimale per la divisione dei compiti tra i componenti dell’applicazione: avrebbe più senso mantenere solo onTodoItemFormSubmit all’interno di App e lasciare tutte le altre funzionalità (lo stato e la sua gestione) dentro TodoItemForm. L’obiettivo di questa sezione è, però, ripassare e consolidare anche l’operazione di sollevamento dello stato, per cui l’abbiamo fatto come esercizio.

Contattaci senza impegno per informazioni sul corso

Pagamento rateale

Valore della rata: A PARTIRE DA 115 €/mese.

Esempio di finanziamento 

Importo finanziato: € 2440 in 24 rate da € 115 – TAN fisso 9,55% TAEG 12,57% – importo totale del credito € 2841.

Il costo totale del credito comprende: interessi calcolati al TAN indicato, oneri fiscali (imposta di bollo sul contratto 16,00 euro*) addebitati sulla prima rata, costo mensile di gestione pratica € 3,90, spesa di istruttoria € 0,00, spesa per invio rendicontazione periodica cartacea € 0,98 (o spesa per invio rendicontazione periodica cartacea € 0,00), imposta di bollo su rendicontazione periodica € 0,00. Modalità di rimborso obbligatoria: addebito diretto su c/c. La scadenza delle rate è determinata dal giorno della liquidazione del contratto; la data di scadenza delle rate è prevista il giorno 15 del mese. L’importo di ciascuna rata comprende una quota di capitale crescente e interessi decrescente secondo un piano di ammortamento “alla francese”. Offerta valida dal 01/01/2024 al 31/12/2024.

Messaggio pubblicitario con finalità promozionale. Per le informazioni precontrattuali richiedere sul punto vendita il documento “Informazioni europee di base sul credito ai consumatori” (SECCI) e copia del testo contrattuale. Salvo approvazione di Sella Personal Credit S.p.A. Aulab S.r.l. opera quale intermediario del credito NON in esclusiva.

*In fase di richiesta del finanziamento verrà proposta la facoltà di selezionare, in alternativa all’imposta di bollo sul contratto di 16,00 euro, l’imposta sostitutiva, pari allo 0,25% dell’importo finanziato.

Pagamento rateale

Valore della rata: A PARTIRE DA 210 €/mese.

Esempio di finanziamento  

Importo finanziato: € 4500 in 24 rate da € 210,03 – TAN fisso 9,68% TAEG 11,97% – importo totale del credito € 5146,55.

Il costo totale del credito comprende: interessi calcolati al TAN indicato, oneri fiscali (imposta di bollo sul contratto 16,00 euro*) addebitati sulla prima rata, costo mensile di gestione pratica € 3,90, spesa di istruttoria € 0,00, spesa per invio rendicontazione periodica cartacea € 0,98 (o spesa per invio rendicontazione periodica cartacea € 0,00), imposta di bollo su rendicontazione periodica € 0,00. Modalità di rimborso obbligatoria: addebito diretto su c/c. La scadenza delle rate è determinata dal giorno della liquidazione del contratto; la data di scadenza delle rate è prevista il giorno 15 del mese. L’importo di ciascuna rata comprende una quota di capitale crescente e interessi decrescente secondo un piano di ammortamento “alla francese”. Offerta valida dal 01/01/2024 al 31/12/2024.

Messaggio pubblicitario con finalità promozionale. Per le informazioni precontrattuali richiedere sul punto vendita il documento “Informazioni europee di base sul credito ai consumatori” (SECCI) e copia del testo contrattuale. Salvo approvazione di Sella Personal Credit S.p.A. Aulab S.r.l. opera quale intermediario del credito NON in esclusiva.

* In fase di richiesta del finanziamento verrà proposta la facoltà di selezionare, in alternativa all’imposta di bollo sul contratto di 16,00 euro, l’imposta sostitutiva, pari allo 0,25% dell’importo finanziato.

Contattaci senza impegno per informazioni sul corso

Scopriamo insieme se i nostri corsi fanno per te. Compila il form e aspetta la chiamata di uno dei nostri consulenti.