Artigo original: React Tutorial – How to Work with Multiple Checkboxes
Lidar com várias caixas de seleção em React é totalmente diferente da forma como você usa as caixas de seleção normais do HTML.
Por isso, neste artigo, veremos como trabalhar com várias caixas de seleção no React.
Você aprenderá:
- Como usar uma caixa de seleção como uma entrada controlada em React
- Como usar os métodos de arrays map e reduce para cálculos complexos
- Como criar um array de tamanho específico pré-preenchido com alguns valores específicos
e muito mais.
Este artigo é parte do curso Mastering Redux (em inglês) do autor. Aqui temos uma prévia do app que é construído durante o curso.
Bem, vamos começar.
Como trabalhar com uma única caixa de seleção
Vamos começar com a funcionalidade de uma única caixa de seleção antes de passarmos para várias caixas de seleção ao mesmo tempo.
Neste artigo, usaremos a sintaxe dos React Hooks para a criação de componentes. Se não estiver familiarizado com React Hooks, confira o artigo do autor: Introduction to React Hooks (em inglês).
Vejamos o código abaixo:
<div className="App">
Select your pizza topping:
<div className="topping">
<input type="checkbox" id="topping" name="topping" value="Paneer" />Paneer
</div>
</div>
Aqui está a demonstração do código no Code Sandbox.
No código acima, acabamos de declarar uma única caixa de seleção, que é semelhante ao modo como declaramos uma caixa de seleção em HTML.
Assim, podemos marcar e desmarcar facilmente a caixa de seleção, conforme vemos abaixo:

No entanto, para exibir na tela se a caixa de seleção está marcada ou não, precisaremos convertê-la em uma entrada controlada.
Em React, a entrada controlada é gerenciada pelo estado, de modo que o valor da entrada possa ser alterado somente mudando o estado relacionado àquela entrada.
Vejamos o código abaixo:
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>
);
}
Aqui temos a demonstração do código no Code Sandbox.
No código acima, declaramos o estado isChecked no componente com o valor inicial de false usando o hook useState:
const [isChecked, setIsChecked] = useState(false);
Em seguida, para o input da caixa de seleção, demos dois props adicionais, checked e onChange, assim:
<input
...
checked={isChecked}
onChange={handleOnChange}
/>
Sempre que clicarmos na caixa de seleção, a função de manipulação handleOnChange será chamada. Será ela que usaremos para definir o valor do estado isChecked.
const handleOnChange = () => {
setIsChecked(!isChecked);
};
Desse modo, se a caixa de seleção estiver marcada, definiremos o valor de isChecked como false. Se, no entanto, a caixa de seleção estiver desmarcada, definimos o valor como true usando !isChecked. Em seguida, passamos esse valor no input da caixa de seleção para a prop checked.
Dessa forma, o input da caixa de seleção se torna uma entrada controlada, cujo valor é gerenciado por um estado.
Observe que, no React, sempre é recomendado usar a entrada controlada em campos de input, mesmo que o código pareça complicado. Isso garante que a mudança do input aconteça somente dentro da função manipuladora onChange.
O estado do input (entrada) não será alterado de nenhuma outra maneira e você sempre terá o valor correto e atualizado do estado do input.
Somente em casos raros você pode usar uma referência do React para usar o input de maneira não controlada.
Como lidar com diversas caixas de seleção
Agora, vamos ver como lidar com várias caixas de seleção.
Vejamos essa demonstração do código no Code Sandbox.

Aqui, estamos exibindo uma lista de sabores (toppings) e seu preço correspondente. Com base nos sabores selecionados, precisamos exibir o valor total.
Anteriormente, com uma única caixa de seleção, tínhamos apenas o estado isChecked e mudávamos o estado da caixa de seleção com base nisso.
Agora, são várias caixas de seleção, de modo que não é mais prático adicionar chamadas diversas de useState para cada caixa de seleção.
Vamos, então, declarar um array no state, indicando o estado de cada caixa de seleção.
Para criar um array de mesmo tamanho que o número de caixas de seleção, podemos usar o método de array fill da seguinte maneira:
const [checkedState, setCheckedState] = useState(
new Array(toppings.length).fill(false)
);
Aqui, declaramos um estado com o valor inicial representado por um array preenchido com o valor false.
Assim, se tivermos 5 sabores, o array com o estado marcado checkedState conterá 5 valores false, assim:
[false, false, false, false, false]
Quando marcarmos/desmarcarmos uma caixa de seleção, mudaremos o valor correspondente de false para true e de true para false.
Aqui está a demonstração do código do Code Sandbox final.
O código do App.js completo tem essa aparência:
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>
);
}
Vamos entender o que estamos fazendo aqui.
Declaramos o input da caixa de seleção, conforme mostrado abaixo:
<input
type="checkbox"
id={`custom-checkbox-${index}`}
name={name}
value={name}
checked={checkedState[index]}
onChange={() => handleOnChange(index)}
/>
Aqui, adicionamos um atributo checked com o valor correspondente a true ou false a partir do estado checkedState. Assim, cada caixa de seleção terá o valor correto de seu estado, marcado ou não.
Também adicionamos uma função manipuladora onChange e estamos passando o index da caixa de seleção, que está marcada/desmarcada, para o método handleOnChange.
O método manipulador handleOnChange terá essa aparência:
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);
};
Aqui, primeiro faremos o loop pelo array checkedState usando o método de array map. Se o valor do parâmetro position passado corresponder ao index atual, invertemos seu valor. Em seguida, se o valor for true, ele será convertido para false usando !item. Se o valor for false, ele será convertido para true.
Se o index não corresponder ao parâmetro position fornecido, não inverteremos o valor, retornando-o como está.
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;
}
});
Usei o operador ternário ?: pois ele reduz o código, mas você pode usar qualquer método de array.
Se não estiver familiarizado com o funcionamento dos métodos de array, como map e reduce, confira este artigo que escrevi (em inglês).
Em seguida, definimos o array checkedState para o array updatedCheckedState. Isso é importante, pois se você não atualizar o estado de checkedState na função manipuladora handleOnChange, não conseguirá marcar/desmarcar a caixa de seleção.
Isso ocorre porque usamos o valor de checkedState para a caixa de seleção para determinar se ela está marcada ou não (já que ela é uma entrada controlada, conforme mostramos abaixo):
<input
type="checkbox"
...
checked={checkedState[index]}
onChange={() => handleOnChange(index)}
/>
Observe que criamos uma variável updatedCheckedState separada e estamos passando aquela variável para a função setCheckedState. Estamos usando o método reduce em updatedCheckedState e não no array checkedState original.
O motivo disso é que, por padrão, a função setCheckedState usadas para atualizar o estado é assíncrona.
O simples fato de chamar a função setCheckedState não garante que você terá o valor atualizado do array checkedState na próxima linha.
Desse modo, criamos uma variável separada e a usamos no método reduce.
Você pode ler este artigo se não estiver familiarizado com a maneira como estados funcionam em React.
Em seguida, para calcular o preço total, usamos o método de array reduce:
const totalPrice = updatedCheckedState.reduce(
(sum, currentState, index) => {
if (currentState === true) {
return sum + toppings[index].price;
}
return sum;
},
0
);
O método de array reduce recebe quatro parâmetros, dos quais usamos apenas três: sum, currentState e index. Você pode usar nomes diferentes, se quiser, já que eles são apenas parâmetros.
Também estamos passando 0 como o valor inicial, que também é conhecido como o valor do acumulador (accumulator) para o parâmetro sum (soma).
Dentro da função de reduce, verificamos se o valor atual do array checkedState é true ou não.
Se for true, isso significa que a caixa de seleção está marcada, então adicionamos o valor de price (preço) correspondente usando sum + toppings[index].price.
Se o valor do array checkedState for false, não adicionamos o preço e retornamos o valor anterior calculado de sum.
Então, definimos o valor de totalPrice para o estado total usando setTotal(totalPrice)
Dessa forma conseguimos calcular corretamente o preço total dos sabores selecionados, como vemos abaixo.

Aqui temos um link para a demonstração no Code Sandbox do código acima para que você o experimente por sua conta.
Obrigado pela leitura!
Muitos desenvolvedores têm dificuldades de entender como funciona o Redux. No entanto, todo desenvolvedor de React deve saber como trabalhar com Redux, já que a maioria dos projetos do setor usa o Redux para gerenciar projetos grandes.
Por isso, para facilitar, o autor lançou um curso chamado Mastering Redux (em inglês).
Neste curso, você aprenderá o Redux do começo e construirá um app de pedido de alimentos completo e do zero com Redux.
Clique na imagem abaixo para se inscrever no curso e obter um desconto de tempo limitado além do meu livro Mastering Modern JavaScript (em inglês) gratuitamente.

Quer ficar atualizado regularmente com o conteúdo relacionado a JavaScript, React, Node.js? Siga o autor no LinkedIn.