I recently build a Checkbox
component, that you can reuse for multiple checkboxes. But what if we want to select all of the checkboxes, and we don't want to click each of them individually?
Let's build a "Select All" checkbox!
See the Pen ReactJS Select All Checkbox by Codemzy (@codemzy) on CodePen.
Checkbox array
I'm going to start with a simple reusable Checkbox
component.
function Checkbox({ name, children }) {
// state
const [checked, setChecked] = React.useState(false);
return (
<div className="my-5">
<input type="checkbox" id={`${name}-checkbox`} name={name} checked={checked} onChange={() => setChecked(!checked)} />
<label for={`${name}-checkbox`}>{children}</label>
</div>
);
};
If you read my recent React checkbox tutorial, this code will be familiar. If you didn't read it and want to understand how the checkbox works, check out the controlled checkbox component section.
For my select all example to work, I need more than one checkbox. So let's start with a list of fruit.
const listOptions = [ "apple", "banana", "cherry", "date", "elderberry", "fig", "honeydew melon"];
Yum!
Now we can map over the array and display our checkboxes.
const listOptions = [ "apple", "banana", "cherry", "date", "elderberry", "fig", "honeydew melon"];
function Checkboxes() {
return (
<div>
<h1>Fruit List</h1>
{ listOptions.map((item) => {
return <Checkbox name={item}>{item}</Checkbox>
}) }
</div>
);
};
And that's all great, but each Checkbox
handles its own state, so only the Checkbox
knows if it is checked, and more importantly, a Checkbox
can only check itself.
Let's fix this, so we can add a "Select All" checkbox.
If you run into any problems with your checkboxes not working, here are a few tips for common bugs with checkboxes not updating in React.
Lifting state up to the Checkboxes
component
Ok, at the moment the Checkbox
component handles its own checked
state. For a select all checkbox to work, I need to pass the state to the Checkbox
component. Because checking the checkbox isn't the only way to check it anymore, I also want to check it when the select all checkbox (that we haven't built yet) is clicked.
Since we have an array of checkboxes, we can't just lift up the state from the Checkbox
component - that just tells us if one checkbox is checked or not, and will be true or false.
Instead, in the parent component (Checkboxes
), we will have a selected
state, that will be an array.
const [selected, setSelected] = React.useState([]);
If the checkbox is selected, we want the name of the item to be in the selected
array, and if it's not, it shouldn't be in the array.
So let's create a function that accepts a value (which will be true or false), and a name, which will be the checkbox name. If the value is true (if (value)
), we will add the name to the selected array, and if it's false ( else
), we will remove the item (with .filter()
).
function handleSelect(value, name) {
if (value) {
setSelected([...selected, name]);
} else {
setSelected(selected.filter((item) => item !== name));
}
};
Ok, nice - here's how our Checkboxes
component is shaping up.
const listOptions = [ "apple", "banana", "cherry", "date", "elderberry", "fig", "honeydew melon"];
function Checkboxes() {
const [selected, setSelected] = React.useState([]);
function handleSelect(value, name) {
if (value) {
setSelected([...selected, name]);
} else {
setSelected(selected.filter((item) => item !== name));
}
};
return (
<div>
<h1>Fruit List</h1>
{ listOptions.map((item) => {
return <Checkbox name={item} value={selected.includes(item)} updateValue={handleSelect}>{item}</Checkbox>
}) }
</div>
);
};
I'm passing two new props to the Checkbox
component. value
and updateValue
. value
returns true if the item is selected, and false if it isn't, by checking if the name exists in the selected
state array (value={selected.includes(item)}
). And updateValue
passes a function as a prop - the handleSelect
function we just created.
Let's wire these props up to our Checkbox
component.
Update the Checkbox
component to accept the new props
Now we have lifted the state up to the Checkboxes
component, instead of having a checked
state on the Checkbox
component, let's instead have it accept the value
and updateValue
props.
function Checkbox({ name, value = false, updateValue = ()=>{}, children }) {
// handle checkbox change
const handleChange = () => {
updateValue(!value, name);
};
return (
<div className="my-5">
<input type="checkbox" id={`${name}-checkbox`} name={name} checked={value} onChange={handleChange} />
<label for={`${name}-checkbox`}>{children}</label>
</div>
);
};
We no longer have any state managed by the Checkbox
component. It just shows if it is checked based on the value
prop (true or false) and has a handleChange
function to call the updateValue
prop with the new value and name.
Remember from the controlled checkbox component, if the value is true
, !value
will set it to false, and vice versa.
Select all checkbox
It took a while to get here, but everything we have done so far has been preparing for this very moment. The "Select All" checkbox.
And we are ready for it!
First, we need a function that updates our selected
state to include the names of all of our checkboxes (in this case, our fruitOptions
list).
function selectAll() {
setSelected(listOptions);
};
And then we can add another Checkbox
at the top of our list which will be our "Select All" checkbox.
<Checkbox name="all" value={selected.length === listOptions.length} updateValue={selectAll}>Select All</Checkbox>
This checkbox is checked if all the other checkboxes are checked (this is true if both the options and the state are the same length value={selected.length === listOptions.length}
, and it triggers our selectAll
function when it is checked.
Here's our updated Checkboxes
component.
// checkboxes component
function Checkboxes() {
const [selected, setSelected] = React.useState([]);
function handleSelect(value, name) {
if (value) {
setSelected([...selected, name]);
} else {
setSelected(selected.filter((item) => item !== name));
}
};
function selectAll() {
setSelected(listOptions);
};
return (
<div>
<h1>Fruit List</h1>
<Checkbox name="all" value={selected.length === listOptions.length} updateValue={selectAll}>Select All</Checkbox>
{ listOptions.map((item) => {
return <Checkbox name={item} value={selected.includes(item)} updateValue={handleSelect}>{item}</Checkbox>
}) }
</div>
);
};
Super!!!
<div id="app"></div>
// checkbox function Checkbox({ name, value = false, updateValue = ()=>{}, children }) { // handle checkbox change const handleChange = () => { updateValue(!value, name); }; // render the checkbox return ( <div className="py-2"> <input type="checkbox" id={`${name}-checkbox`} name={name} checked={value} onChange={handleChange} /> <label for={`${name}-checkbox`} className="ml-1 capitalize">{children}</label> </div> ); }; // checkbox list const listOptions = [ "apple", "banana", "cherry", "date", "elderberry", "fig", "honeydew melon"]; // checkboxes component function Checkboxes() { const [selected, setSelected] = React.useState([]); // for updating state on check function handleSelect(value, name) { if (value) { setSelected([...selected, name]); } else { setSelected(selected.filter((item) => item !== name)); } }; // select all function function selectAll() { setSelected(listOptions); }; // render checkboxes return ( <div className="flex w-full min-h-screen items-center justify-center p-5"> <div className="w-full max-w-md"> <h1 className="font-semibold text-lg mb-2">Checkbox List</h1> <div className="-mx-5 px-5 py-0 rounded bg-gray-100 font-medium"> <Checkbox name="all" value={selected.length === listOptions.length} updateValue={selectAll}>Select All</Checkbox> </div> { listOptions.map((item) => { return <Checkbox name={item} value={selected.includes(item)} updateValue={handleSelect}>{item}</Checkbox> }) } </div> </div> ) }; // react render ReactDOM.render( <Checkboxes />, document.getElementById('app') );
Uncheck all
That's all working great. But I want to add one extra function, which I think will be pretty useful for users of our checkboxes.
Imagine you accidentally click the "Select All" checkbox. Now you have to go through and unselect all of the checkboxes you didn't want selected. No big deal if there's only a handful of checkboxes selected, but if there were 20 or more, you might be pretty annoyed!
So I'd like to add a function that unchecks all of the checkboxes if they are all selected. An "Unselect All" option if you like.
I think the best way would be that if the "Select All" checkbox is checked, clicking it unselects all (and unchecks all the checkboxes).
That's kinda how single checkboxes work, if they checked, clicking them does the opposite, so I think it makes sense to do things that way rather than having a separate checkbox for "Unselect All".
Let's code this up.
All we really need to do is update our selectAll
function.
Let's not forget that it will receive a value
argument from the Checkbox
component, just like our handleSelect
function does. If the "Select All" checkbox isn't checked, value
will be true
, and we should select all the checkboxes (like we do already).
But if the "Select All" checkbox is checked, then selectAll
will be called with false
, and we can unselect all the checkboxes by emptying the selected
array.
function selectAll(value) {
if (value) { // if true
setSelected(listOptions); // select all
} else { // if false
setSelected([]); // unselect all
}
};
Here's the updated Checkboxes
component:
// checkboxes component
function Checkboxes() {
const [selected, setSelected] = React.useState([]);
function handleSelect(value, name) {
if (value) {
setSelected([...selected, name]);
} else {
setSelected(selected.filter((item) => item !== name));
}
};
function selectAll(value) {
if (value) { // if true
setSelected(listOptions); // select all
} else { // if false
setSelected([]); // unselect all
}
};
return (
<div>
<h1>Fruit List</h1>
<Checkbox name="all" value={selected.length === listOptions.length} updateValue={selectAll}>Select All</Checkbox>
{ listOptions.map((item) => {
return <Checkbox name={item} value={selected.includes(item)} updateValue={handleSelect}>{item}</Checkbox>
}) }
</div>
);
};
<div id="app"></div>
// checkbox function Checkbox({ name, value = false, updateValue = ()=>{}, children }) { // handle checkbox change const handleChange = () => { updateValue(!value, name); }; // render the checkbox return ( <div className="py-2"> <input type="checkbox" id={`${name}-checkbox`} name={name} checked={value} onChange={handleChange} /> <label for={`${name}-checkbox`} className="ml-1 capitalize">{children}</label> </div> ); }; // checkbox list const listOptions = [ "apple", "banana", "cherry", "date", "elderberry", "fig", "honeydew melon"]; // checkboxes component function Checkboxes() { const [selected, setSelected] = React.useState([]); // for updating state on check function handleSelect(value, name) { if (value) { setSelected([...selected, name]); } else { setSelected(selected.filter((item) => item !== name)); } }; // select all function function selectAll(value) { if (value) { // if true setSelected(listOptions); // select all } else { // if false setSelected([]); // unselect all } }; // render checkboxes return ( <div className="flex w-full min-h-screen items-center justify-center p-5"> <div className="w-full max-w-md"> <h1 className="font-semibold text-lg mb-2">Checkbox List</h1> <div className="-mx-5 px-5 py-0 rounded bg-gray-100 font-medium"> <Checkbox name="all" value={selected.length === listOptions.length} updateValue={selectAll}>Select All</Checkbox> </div> { listOptions.map((item) => { return <Checkbox name={item} value={selected.includes(item)} updateValue={handleSelect}>{item}</Checkbox> }) } </div> </div> ) }; // react render ReactDOM.render( <Checkboxes />, document.getElementById('app') );