Articolo originale: https://www.freecodecamp.org/news/how-to-work-with-multiple-checkboxes-in-react/

Gestire caselle a scelta multipla in React è completamente diverso da come usi normali caselle a scelta multipla in HTML.

Quindi in questo articolo vedremo come lavorare con varie caselle a scelta multipla in React.

Imparerai a:

  • Come usare le caselle a scelta multipla come Input Controllati in React
  • Come usare i metodi per array map e reduce per calcoli complessi
  • Come creare un array di una specifica lunghezza preriempito con uno specifico valore

e molto altro.

Quindi, iniziamo.

Come lavorare con una singola casella

Iniziamo con la funzionalità di una singola casella prima di muoverci a multiple caselle a scelta multipla.

In questo articolo userò gli Hook di React per creare i componenti, se non sei familiare, puoi leggere questa guida per principianti sugli Hook di React.

Dai un'occhiata al codice qua sotto:

<div className="App">
  Select your pizza topping:
  <div className="topping">
    <input type="checkbox" id="topping" name="topping" value="Paneer" />Paneer
  </div>
</div>

Ecco una demo in Code Sandbox.

Nel codice qua sopra abbiamo dichiarato una singola casella in maniera simile a come si dichiara una casella a scelta multipla in HTML.

Quindi possiamo facilmente selezionare e deselezionare la casella come mostrato sotto:

check_uncheck-1

Ma per mostrare sullo schermo se è selezionata o meno dobbiamo convertirla ad un Input Controllato.

In React un Input Controllato è gestito dallo stato, quindi il valore dell'input può essere cambiato solo cambiando lo stato legato a quell'input.

Dai un'occhiata al codice qua sotto:

export default function App() {
  const [isChecked, setIsChecked] = useState(false);

  const handleOnChange = () => {
    setIsChecked(!isChecked);
  };

  return (
    <div className="App">
      Select your pizza topping:
      <div className="topping">
        <input
          type="checkbox"
          id="topping"
          name="topping"
          value="Paneer"
          checked={isChecked}
          onChange={handleOnChange}
        />
        Paneer
      </div>
      <div className="result">
        Above checkbox is {isChecked ? "checked" : "un-checked"}.
      </div>
    </div>
  );
}

Ecco una demo in Code Sandbox.

Nel codice sopra, abbiamo dichiarato lo stato isChecked nel componente con un valore iniziale di false usando l'hook useState:

const [isChecked, setIsChecked] = useState(false);

Poi abbiamo dato due proprietà in più alla casella a scelta multipla, checked e onChange, in questo modo:

<input
  ...
  checked={isChecked}
  onChange={handleOnChange}
/>

Ogni volta che clicchiamo la casella, la funzione handleOnChange viene invocata ed usata per impostare il valore dello stato isChecked.

const handleOnChange = () => {
  setIsChecked(!isChecked);
};

Quindi se la casella è selezionata, impostiamo il valore di isChecked a false, se invece non è selezionata, impostiamo il valore a true usando !isChecked. Quindi poi passiamo il valore alla casella per la proprietà checked.

In questo modo la casella input diventa un input controllato il cui valore è gestito dallo stato.

Nota che in React è sempre raccomandato usare Input Controllati per campi di input anche se il codice sembra complicato. Questo garantisce che l'input venga cambiato solo dentro onChange.

Lo stato dell'input non verrà cambiato in nessun altro modo e otterrai sempre il valore corretto e aggiornato dello stato del tuo input.

Solo in certi casi puoi usare React ref per usare l'input in modo non controllato.

Come gestire caselle multiple

Ora, vediamo come gestire più caselle a scelta multipla.

Guarda questa demo su Code Sandbox.

multiple_checkboxes-2

Qui, stiamo mostrando una lista di aggiunte con il loro prezzo corrispondente e a seconda di quali sono selezionate dobbiamo mostrare il costo totale.

In precedenza, con una singola casella, avevamo solo lo stato isChecked e cambiavamo lo stato della casella basato su quello.

Ma ora abbiamo svariate caselle, quindi non è pratico aggiungere multiple invocazioni a useState per ogni casella.

Quindi dichiariamo un array nello stato che racchiude lo stato di ogni casella a scelta multipla.

Per creare un array di lunghezza uguale al numero di caselle, possiamo usare il metodo per array fill in questo modo:

const [checkedState, setCheckedState] = useState(
    new Array(toppings.length).fill(false)
);

Qui abbiamo dichiarato uno stato con un valore iniziare di un array riempito con il valore false.

Quindi se abbiamo 5 aggiunte allora lo stato checkedState conterrà cinque valori false in questo modo:

[false, false, false, false, false]

E una volta che selezioniamo/deselezioniamo una casella cambieremo il corrispondente false in true.

Ecco una finale su Code Sandbox.

Il codice completo di App.js è come questo:

import { useState } from "react";
import { toppings } from "./utils/toppings";
import "./styles.css";

const getFormattedPrice = (price) => `$${price.toFixed(2)}`;

export default function App() {
  const [checkedState, setCheckedState] = useState(
    new Array(toppings.length).fill(false)
  );

  const [total, setTotal] = useState(0);

  const handleOnChange = (position) => {
    const updatedCheckedState = checkedState.map((item, index) =>
      index === position ? !item : item
    );

    setCheckedState(updatedCheckedState);

    const totalPrice = updatedCheckedState.reduce(
      (sum, currentState, index) => {
        if (currentState === true) {
          return sum + toppings[index].price;
        }
        return sum;
      },
      0
    );

    setTotal(totalPrice);
  };

  return (
    <div className="App">
      <h3>Select Toppings</h3>
      <ul className="toppings-list">
        {toppings.map(({ name, price }, index) => {
          return (
            <li key={index}>
              <div className="toppings-list-item">
                <div className="left-section">
                  <input
                    type="checkbox"
                    id={`custom-checkbox-${index}`}
                    name={name}
                    value={name}
                    checked={checkedState[index]}
                    onChange={() => handleOnChange(index)}
                  />
                  <label htmlFor={`custom-checkbox-${index}`}>{name}</label>
                </div>
                <div className="right-section">{getFormattedPrice(price)}</div>
              </div>
            </li>
          );
        })}
        <li>
          <div className="toppings-list-item">
            <div className="left-section">Total:</div>
            <div className="right-section">{getFormattedPrice(total)}</div>
          </div>
        </li>
      </ul>
    </div>
  );
}

Capiamo cosa stiamo facendo qua.

Abbiamo dichiarato la singola casella a scelta multipla come mostrato sotto:

<input
  type="checkbox"
  id={`custom-checkbox-${index}`}
  name={name}
  value={name}
  checked={checkedState[index]}
  onChange={() => handleOnChange(index)}
/>

Qui, abbiamo aggiunto l'attributo checked con il valore corrispondente di true o false dallo stato checkedState in modo che ogni casella mostrerà correttamente se è selezionata o meno.

Abbiamo anche aggiunto la proprietà onChange e passiamo come argomento l'index della casella che è selezionata/deselezionata al metodo handleOnChange.

Il metodo handleOnChange appare così:

const handleOnChange = (position) => {
  const updatedCheckedState = checkedState.map((item, index) =>
    index === position ? !item : item
  );

  setCheckedState(updatedCheckedState);

  const totalPrice = updatedCheckedState.reduce(
    (sum, currentState, index) => {
      if (currentState === true) {
        return sum + toppings[index].price;
      }
      return sum;
    },
    0
  );

  setTotal(totalPrice);
};

Qui prima iteriamo sull'array checkedState usando il metodo per array map. Se il valore passato come parametro position combacia con l'index corrente, allora invertiamo il suo valore usando !item (se è false in true, se è true in false).

Se il valore di index non combacia con il parametro position allora non cambiamo il valore, ma lo restituiamo così com'è.

const updatedCheckedState = checkedState.map((item, index) =>
  index === position ? !item : item
);

// the above code is the same as the below code

const updatedCheckedState = checkedState.map((item, index) => {
  if (index === position) {
    return !item;
  } else {
    return item;
  }
});

Ho usato l'operatore ternario ?: perché rende il codice più compatto, ma puoi usare qualsiasi metodo per array.

Se non hai familiarità con come funzionano metodi per array quali map o reduce, allora guarda questo articolo che ho scritto.

Successivamente, stiamo impostando l'array checkedState all'array updatedCheckedState, questo è importante perché se non cambi checkedState dentro handleOnChange allora non puoi selezionare/deselezionare le caselle.

Questo è perché stiamo usando il valore di checkedState per la casella per determinare se la casella è selezionata o meno (visto che è un input controllato come mostrato sotto):

<input
  type="checkbox"
  ...
  checked={checkedState[index]}
  onChange={() => handleOnChange(index)}
/>

Nota che abbiamo creato una variabile updatedCheckedState separata e stiamo passando questa variabile alla funzione setCheckedState e stiamo usando il metodo reduce su updatedCheckedState e non sull'array checkedState originale.

Questo è perché, di default, la funzione setCheckedState usata per aggiornare lo stato è asincrona.

Non c'è garanzia che solo perché hai invocato la funzione setCheckedState allora ottieni il valore aggiornato di checkedState sulla riga successiva.

Quindi abbiamo creato una variabile separata e usato il metodo reduce su quella.

Puoi leggere questo articolo se non hai familiarità su come funziona lo stato in React.

Quindi per calcolare il prezzo totale stiamo usando il metodo reduce:

const totalPrice = updatedCheckedState.reduce(
  (sum, currentState, index) => {
    if (currentState === true) {
      return sum + toppings[index].price;
    }
    return sum;
  },
  0
);

La callback di reduce riceve quattro parametri, di cui ne stiamo usando solo tre: sum, currentState e index. Puoi usare nomi diversi se vuoi visto che sono solo parametri.

Stiamo anche passando 0 come valore iniziale del parametro sum.

Quindi dentro la funzione stiamo controllando se il valore corrente dell'array checkedState è true o meno.

Se è true, questo significa che la casella è selezionata e quindi stiamo aggiungendo il valore corrispondente del prezzo con sum + toppings[index].price.

Se il valore dell'array checkedState è false allora non aggiungiamo il prezzo, ma restituiamo il valore calcolato precedentemente di sum.

Quindi stiamo dando il valore di totalPrice allo stato total usando setTotal(totalPrice).

In questo modo possiamo calcolare correttamente il valore totale delle aggiunte selezionate come puoi vedere sotto.

toppings-1

Ecco un Preview link della demo su Sandbox per provarlo tu stesso.

Grazie per aver letto!