Artículo original escrito por Yogesh Chavan
Artículo original React Tutorial – How to Work with Multiple Checkboxes
Traducido y adaptado por Fernando Cañas
Manipular múltiples checkbox (casillas de verificación) en React es completamente diferente a su uso regular en HTML.
En este artículo veremos cómo trabajar con múltiples checkbox en React.
Aprenderás:
- Cómo usar un checkbox como un input controlado en React.
- Cómo usar los métodos map y reduce de los arreglos (arrays) para ejecutar cálculos complejos.
- Cómo crear un arreglo pre-llenado de longitud específica con algunos valores específicos.
Y mucho más.
Este artículo es una parte de mi curso "Mastering Redux". Aquí puedes acceder a una previsualización de la aplicación que estaremos construyendo en el curso.
Empecemos.
Cómo trabajar con un único checkbox
Empecemos con la funcionalidad de un único checkbox antes de avanzar a múltiples checkboxes.
En este artículo usaremos la sintaxis de React Hooks para crear componentes.
Miremos el siguiente código:
<div className="App">
Select your pizza topping:
<div className="topping">
<input type="checkbox" id="topping" name="topping" value="Paneer" />Paneer
</div>
</div>
Aquí un demo en Sandbox.
En el código anterior, declaramos solamente un único checkbox de una forma muy similar a como lo haríamos en HTML.
De esta manera es muy fácil marcar y desmarcar el checkbox, tal como se muestra a continuación:
Pero para mostrar en pantalla si la casilla se encuentra marcada o no, necesitamos convertir el checkbox en un input controlado.
En React, un input controlado es manejado por el estado, de tal manera que el valor del input solamente pueda ser cambiado si se modifica el estado relacionado con ese input.
Mira el siguiente caso:
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>
);
}
Aquí puedes ver el código en Sandbox.
En el código de arriba, hemos declarado el estado isChecked
en el componente con un valor inicial en false
haciendo uso del hook useState
.
const [isChecked, setIsChecked] = useState(false);
Luego, al input de tipo checkbox le hemos dado dos props adicionales, checked
y onChange
:
<input
...
checked={isChecked}
onChange={handleOnChange}
/>
Cada vez que hacemos clic en el checkbox, la función handleOnChange
será ejecutada. Esto hará que modifiquemos el valor del estado isChecked
:
const handleOnChange = () => {
setIsChecked(!isChecked);
};
Si el checkbox se encuentra marcado, cambiaremos el valor de isChecked
a false
. Pero si el checkbox se encuentra desmarcado, definiremos el estado como true
usando !isChecked
. Luego pasamos este valor en el input a través de la prop checked
.
De esta manera el input de tipo checkbox se convierte en un input controlado cuyo valor es gestionado por el estado.
En React siempre se recomienda usar inputs controlados aun si el código se ve visualmente complicado. Esto garantiza que los cambios en los inputs solamente ocurran al interior de la función ligada al evento onChange
.
El estado del input no se verá afectado de ninguna otra manera y así siempre obtendrás el valor correcto y actualizado en el input.
Solo en casos raros puedes usar React ref para usar el input de forma no controlada.
Cómo manejar múltiples checkbox
Ahora démosle una mirada a cómo manipular múltiples checkbox.
Mira el siguiente código en Sandbox:
Aquí, estamos mostrando una lista de ingredientes y su precio correspondiente. Basados en los ingredientes que sean seleccionados, necesitamos mostrar el valor total.
Previamente, con un único checkbox, solamente teníamos el estado isChecked
y con ello podíamos cambiar el estado.
Sin embargo, ahora tenemos muchos checkbox, así que no es práctico agregar múltiples useState
para cada uno de ellos.
Entonces, declaremos un arreglo dentro del estado indicando la marcación de cada uno de los checkbox.
Para crear un arreglo de longitud igual al número de checkbox, podemos usar el método fill
de los arreglos así:
const [checkedState, setCheckedState] = useState(
new Array(toppings.length).fill(false)
);
Aquí hemos declarado un estado con un valor inicial igual a un arreglo cuyos valores son false
.
Así que si tenemos 5 ingredientes, entonces el estado checkedState
será un arreglo con 5 valores en false
.
[false, false, false, false, false]
Ahora, una vez que marquemos (y desmarquemos) el checkbox, cambiaremos el valor false
correspondiente por true
(y viceversa).
Aquí está el Sandbox final. El código App.js
completo de se ve así:
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>
);
}
Entendamos lo que estamos haciendo aquí.
Hemos declarado el input tipo checkbox de la siguiente forma:
<input
type="checkbox"
id={`custom-checkbox-${index}`}
name={name}
value={name}
checked={checkedState[index]}
onChange={() => handleOnChange(index)}
/>
Aquí, hemos agregado el atributo checked
con el correspondiente valor de true
o false
según lo indique el estado en checkedState
. Así cada checkbox tendrá el valor correcto para su estado de marcado.
También hemos agregado una función que maneja el evento onChange
y hemos pasado el index
del checkbox, de tal manera que este sea marcado o desmarcado por el método handleOnChange
.
El método handleOnChange
se ve de la siguiente manera:
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);
};
Aquí, estamos primero recorriendo el arreglo checkedState
usando el método map
de los arreglos. Si el valor del parámetro position
que ha sido pasado a la función es igual al index
de turno, entonces modificamos su valor. Así, si el valor es true
entonces será convertido a false
usando !item
y viceversa.
Si el index
no es igual al proveído por el parámetro position
, entonces no estamos invirtiendo su valor y devolveremos su valor tal cual se encuentre:
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;
}
});
Usé el operador ternario ?:
porque permite que el código sea más corto, pero tú puedes usar el método de los arreglos que prefieras.
Si no estás familiarizado con cómo funcionan los métodos como map
o reduce
, entonces revisa este otro artículo que escribí.
A continuación, definiremos el arreglo checkedState
según lo registrado en el arreglo updatedCheckedState
. Esto es importante porque si no actualizas el estado checkedState
dentro de la función handleOnChange
, entonces no podrás marcar o desmarcar los checkbox.
Esto es así porque estamos usando el valor de checkedState
para que los checkbox puedan determinar si están marcados o desmarcados (como un input controlado, tal cual como se muestra a continuación):
<input
type="checkbox"
...
checked={checkedState[index]}
onChange={() => handleOnChange(index)}
/>
Mira que hemos creado una variable separada updatedCheckedState
y que estamos pasando esta variable a la función setCheckedState
. También estamos usando el método reduce
sobre updatedCheckedState
y no sobre el arreglo checkedState
con el estado original.
Esto es porque, por defecto, la función setCheckedState
suele actualizar el estado de forma asíncrona.
Sólo porque hallas llamado a la función setCheckedState
no se garantiza que vas a obtener el valor actualizado de checkedState
en la siguiente línea.
Así que hemos creado una variable separada y la hemos usado para ejecutar el método reduce
.
Luego, para calcular el precio total, estamos utilizando el arreglo reduce
:
const totalPrice = updatedCheckedState.reduce(
(sum, currentState, index) => {
if (currentState === true) {
return sum + toppings[index].price;
}
return sum;
},
0
);
El método reduce
recibe cuatro parámetros, de los cuales solamente estamos usando tres: sum
, currentState
e index
. Puedes usar nombres diferentes si quieres, ya que son simplemente parámetros.
También estamos pasando 0
como valor inicial, lo que también es conocido como el valor accumulator
para el parámetro de suma sum
.
Luego, dentro de la función reduce
, estamos revisando si el valor actual del arreglo checkedState
es true
o no.
Si es true
, significa que el checkbox está marcado, así que agregaremos el valor del price
correspondiente usando la operación sum + toppings[index].price
.
Si el valor en el arreglo checkedState
es false
, entonces no agregaremos su precio y devolveremos el valor previamente calculado de sum
.
Así podemos asignar el valor de totalPrice
al estado total
usando setTotal(totalPrice)
.
De esta manera estamos calculando correctamente el valor total de los ingredientes seleccionados como ves a continuación:
Aquí hay un link para previsualizar el demo en Sandbox.
¡Gracias por leer!
Muchos desarrolladores pasan dificultades entendiendo cómo trabaja Redux. Pero todo desarrollador que use React debería tener claro cómo trabajar con Redux, ya que los proyectos en la industria usan principalmente Redux para administrar proyectos grandes.
Así que para hacértelo más fácil, he lanzando el curso Mastering Redux.
En este curso aprenderás Redux desde un nivel absolutamente principiante y construirás una food ordering app desde cero usando Redux.
Dale clic a la imagen de abajo para unirte al curso, obtener un descuento con tiempo limitado y mi libro popular Mastering Modern JavaScript gratis.
¿Quieres estar actualizado con contenido regular sobre JavaScript, React y Node.js? Sígueme en LinkedIn.