Оригінальна публікація: React Tutorial – How to Work with Multiple Checkboxes

Робота з кількома прапорцями в React повністю відрізняється від того, як ви використовуєте звичайні прапорці HTML.

Тому в цьому посібнику ми побачимо, як працювати з кількома прапорцями в React.

Ви дізнаєтесь:

  • як використовувати прапорець як контрольований компонент у React
  • як використовувати методи масиву map та reduce для складних обчислень
  • як створити масив певної довжини, попередньо заповнений певним значенням

та багато іншого.

Ця стаття є частиною мого курсу Mastering Redux. Ось попередній перегляд програми, яку ми будемо створювати на курсі.

Тож почнемо.

Як працювати з одним прапорцем

Давайте почнемо з функціональності одного прапорця, перш ніж переходити до кількох.

У цій статті я буду використовувати синтаксис React Hooks для створення компонентів. Отже, якщо ви не знайомі з хуками React, перегляньте мою статтю «Вступ до хуків React».

Подивіться на наведений нижче код:

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

Ось демоверсія на Code Sandbox.

У наведеному вище коді ми щойно оголосили один прапорець, який подібний до того, як ми оголошуємо прапорець HTML.

Тож ми можемо легко встановити та зняти прапорець, як показано нижче:

check_uncheck-1

Але щоб відобразити на екрані позначений прапорець чи ні, нам потрібно перетворити його на контрольований компонент.

У React контрольований компонент керується станом, тому вхідне значення можна змінити, лише змінивши стан, пов’язаний із цим введенням.

Подивіться на наведений нижче код:

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>
  );
}

Ось демоверсія на Code Sandbox.

У наведеному вище коді ми оголосили стан isChecked у компоненті з початковим значенням false за допомогою хука useState :

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

Потім для прапорця вводу ми встановили два додаткові параметри checked та onChange таким чином:

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

Щоразу, коли ми натискаємо прапорець, буде викликана функція обробки handleOnChange, яку ми використовуємо для встановлення значення стану  isChecked.

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

Отже, якщо прапорець позначено, ми встановлюємо значення isChecked на false. Але якщо прапорець знятий, ми встановлюємо значення true за допомогою !isChecked. Потім ми передаємо це значення в прапорець введення для перевіреного параметру checked.

Таким чином прапорець вводу стає контрольованим компонентом, значенням якого керує стан.

Зверніть увагу, що в React завжди рекомендується використовувати контрольовані компоненти для полів введення, навіть якщо код виглядає складним. Це гарантує, що зміна вхідних даних відбувається лише всередині обробника onChange.

Стан введення не буде змінено будь-яким іншим способом, і ви завжди отримуватимете правильне й оновлене значення стану введення.

Лише в рідкісних випадках ви можете використовувати React ref для неконтрольованого використання компоненту.

Як обробляти кілька прапорців

Тепер давайте розглянемо, як ви будете обробляти кілька прапорців.

Подивіться на приклад у Code Sandbox.

multiple_checkboxes-2

Тут ми показуємо список топінгів і відповідну ціну. Виходячи з того, які топінги вибрано, нам потрібно відобразити загальну суму.

Раніше з одним прапорцем ми мали лише стан isChecked, і ми змінювали стан прапорця на основі цього.

Але тепер у нас багато прапорців, тому додавати кілька викликів useState для кожного прапорця непрактично.

Отже, давайте оголосимо масив у стані, що вказує стан кожного прапорця.

Щоб створити масив, довжина якого дорівнює кількості прапорців, ми можемо використати метод fill:

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

Тут ми оголосили стан із початковим значенням у вигляді масиву, заповненого значенням false.

Отже, якщо ми маємо 5 топінгів, масив станів checkedState міститиме 5 значень false, як наведено нижче:

[false, false, false, false, false]

І як тільки ми поставимо/знімемо прапорець, ми змінимо відповідний стан  false на true або true на false.

Тут наведено остаточний код на Code Sandbox.

Повний код App.js виглядає так:

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>
  );
}

Давайте розберемося, що ми тут робимо.

Ми оголосили прапорець вводу, як показано нижче:

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

Тут ми додали атрибут checked із відповідним значенням true або false зі стану checkedState. Отже, кожен прапорець матиме правильне значення свого позначеного стану.

Ми також додали обробник onChange і передаємо index прапорця, який позначено/знято, до методу handleOnChange.

Обробник методу handleOnChange має наступний вигляд:

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);
};

Тут ми спочатку спочатку проходимось по масиву checkedState, використовуючи метод масивів map. Якщо значення переданого параметра position збігається з поточним індексом, ми змінюємо його значення. Потім, якщо значення є true, воно буде перетворено на false за допомогою !item, а якщо значення false — воно буде перетворено на true.

Якщо index не збігається з наданим параметром position, ми не змінюємо його значення, а просто повертаємо значення, яким воно є.

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;
  }
});

Я використовував тернарний оператор ?:, оскільки він робить код коротшим, але ви можете використовувати будь-який метод масиву.

Якщо ви не знайомі з тим, як працюють такі методи масиву, як map чи reduce, то перегляньте цю статтю, яку я написав.

Далі ми встановлюємо масив checkedState до оновленого масиву updatedCheckedState. Це важливо, тому що якщо ви не оновите стан checkedState в обробнику handleOnChange, ви не зможете встановити або зняти прапорець.

Це пояснюється тим, що ми використовуємо значення checkedState для прапорця, щоб визначити, чи позначено прапорець чи ні (оскільки це контрольований компонент, як показано нижче):

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

Зауважте, що ми створили окрему змінну updatedCheckedState і передаємо цю змінну функції setCheckedState. Ми використовуємо метод reduce для updatedCheckedState, а не для вихідного масиву checkedState.

Причина в тому, що за замовчуванням функція setCheckedState, яка використовується для оновлення стану, є асинхронною.

Виклик функції setCheckedState не гарантує, що ви отримаєте оновлене значення масиву checkedState у наступному рядку.

Тож ми створили окрему змінну та використали її в методі reduce.

Ви можете прочитати цю статтю, якщо ви не знайомі з тим, як стан працює в React.

Потім, щоб обчислити загальну суму, ми використовуємо метод масиву reduce:

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

Метод масиву reduce отримує чотири параметри, з яких ми використовуємо лише три: sum, currentState та index. Ви можете використовувати інші назви, якщо хочете, оскільки це лише параметри.

Ми також передаємо 0 як початкове значення, яке також відоме як значення accumulator для параметру sum.

Потім у функції reduce ми перевіряємо, чи поточне значення масиву checkedState дійсно true.

Якщо значення true, це означає, що прапорець позначено, тому ми додаємо значення відповідного price за допомогою sum + toppings[index].price.

Якщо значення масиву checkedState є false, ми не додаємо його ціну, а просто повертаємо розраховане попереднє значення sum.

Потім ми встановлюємо значення totalPrice до стану total за допомогою setTotal(totalPrice)

Таким чином ми можемо правильно розрахувати загальну суму для обраних топінгів, як ви бачите нижче.

toppings-1

Ось посилання на попередній перегляд наведеного вище прикладу на Code Sandbox, щоб спробувати самостійно.

Дякую, що прочитали!

Більшості розробників важко зрозуміти, як працює Redux. Але кожен розробник React повинен знати, як працювати з Redux, оскільки промислові проєкти здебільшого використовують Redux для керування більшими проєктами.

Тому, щоб вам було легше, я запустив курс Mastering Redux.

У цьому курсі ви вивчите Redux з самого початку, а також створите повноцінний застосунок для замовлення їжі з нуля за допомогою Redux.

Приєднуйтесь до курсу та отримуйте обмежену пропозицію знижки, а також мою популярну книгу «Опанування сучасного JavaScript» безкоштовно.

banner

Хочете бути в курсі регулярного контенту по JavaScript, React, Node.js? Слідкуйте за мною в LinkedIn.