BlogReactJS

How to build a select all checkbox with ReactJS

Written by Codemzy on August 21st, 2023

Here's how I build a simple select all checkbox with React using the `useState` hook and lifting the state up out of the checkbox component. Now we can check all and uncheck all too!

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 selectAllwill 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')
);