React Checkboxes

In the last lesson you learned about React Radio Buttons, so you can already pick one option out of many. This lesson is about checkboxes, the simple on or off switch that tracks a checked state instead of a value.

🤔 Why are checkboxes different?

A checkbox is not asking “what did you type?”. It’s asking “is this on or off?”. So the data is a different shape.

  • A text box holds text, so you read e.target.value to get what’s inside.
  • A checkbox holds a yes or no, so you read e.target.checked, which is a boolean (true or false).
  • The thing tied to the box is checked, not value. That’s the part people forget.

So the rule is simple. For a checkbox, think checked and e.target.checked, not value.

❌ The mistake: using value on a checkbox

Most people come from text inputs, so they reach for value out of habit. On a checkbox that just does not work. Here’s the wrong way next to the right way, so you can see the swap.

// ❌ Wrong - value and e.target.value do not control a checkbox
<input
type="checkbox"
value={agreed}
onChange={(e) => setAgreed(e.target.value)}
/>
// ✅ Right - checked and e.target.checked control a checkbox
<input
type="checkbox"
checked={agreed}
onChange={(e) => setAgreed(e.target.checked)}
/>

Here’s why the second one is correct.

  • checked={agreed} ties the box to a boolean in state. So when agreed is true, the box shows a tick.
  • e.target.checked is true when the box is ticked and false when it’s not. That’s exactly what you want to save.
  • e.target.value on a checkbox gives you a fixed string like "on", not a true or false, so it never tracks the real state.

So checked controls the look, and e.target.checked reads the real state. That’s the whole pattern.

☑️ A single checkbox bound to a boolean

Let’s build the classic “I agree to the terms” checkbox. It holds one agreed boolean, and the button only works when the box is ticked.

import { useState } from "react";
function TermsForm() {
const [agreed, setAgreed] = useState(false);
return (
<div>
<label>
<input
type="checkbox"
checked={agreed}
onChange={(e) => setAgreed(e.target.checked)}
/>
I agree to the terms
</label>
<button disabled={!agreed}>Continue</button>
</div>
);
}

Let’s read it step by step.

  • We start with useState(false), so the box begins unticked.
  • checked={agreed} makes the box match the state, so the two stay in sync.
  • On every click, onChange saves e.target.checked into agreed, so it goes true then false then true.
  • disabled={!agreed} keeps the button off until the user ticks the box. Nice and clean.

Wrap it in a label

Putting the checkbox inside a <label> means clicking the text ticks the box too, not just the small square. It’s friendlier and it costs you nothing.

✅☑️ A group of checkboxes in an array

A single boolean is fine for one switch. But what about “pick the toppings you want” where the user can tick several at once? For that we keep the selected values in an array, and each ticked box adds its value to a selected array in state.

import { useState } from "react";
function Toppings() {
const options = ["Cheese", "Mushroom", "Olives"];
const [selected, setSelected] = useState([]);
function handleChange(e) {
const value = e.target.value;
if (e.target.checked) {
setSelected([...selected, value]); // add it
} else {
setSelected(selected.filter((item) => item !== value)); // remove it
}
}
return (
<div>
{options.map((option) => (
<label key={option}>
<input
type="checkbox"
value={option}
checked={selected.includes(option)}
onChange={handleChange}
/>
{option}
</label>
))}
<p>You picked: {selected.join(", ")}</p>
</div>
);
}

Now here’s the clever bit. In a group, each box still uses checked to control itself, but we also give it a value so we know which option it is.

  • value={option} labels each box with its own option, like “Cheese” or “Olives”.
  • checked={selected.includes(option)} ticks the box only if that option is already in the array.
  • When a box is ticked, e.target.checked is true, so we add e.target.value with spread: [...selected, value].
  • When a box is unticked, e.target.checked is false, so we drop that value with filter.

So the array grows when you tick and shrinks when you untick. Add with spread, remove with filter, just like you learned with arrays in state.

Output

(user ticks "Cheese", then "Olives")
You picked: Cheese, Olives
(user unticks "Cheese")
You picked: Olives

Don't mutate the array

Use [...selected, value] to add and filter to remove. Don’t reach for push or splice on the state array, because those change the same array in place and React may not re-render. Always hand React a new array.

🧩 What You’ve Learned

  • ✅ A checkbox is an on or off switch, so it works with a boolean, not text
  • ✅ Use checked to control a checkbox, never value
  • ✅ Read the state with e.target.checked inside onChange, not e.target.value
  • ✅ A single checkbox binds to one boolean: checked={agreed} plus setAgreed(e.target.checked)
  • ✅ A group of checkboxes keeps the picked values in an array in state
  • ✅ Add a value with spread when ticked, remove it with filter when unticked
  • ✅ For a group, give each box a value, and tick it with checked={selected.includes(option)}

Check Your Knowledge

Test what you learned. Pick an answer for each question, then click Check.

  1. 1

    Which attribute controls a checkbox in React?

    Why: A checkbox is on or off, so it is controlled by checked, which takes a boolean. value is for text inputs, not checkboxes.

  2. 2

    Inside onChange, how do you read whether a checkbox is ticked?

    Why: e.target.checked is true when the box is ticked and false when it is not. e.target.value on a checkbox just gives a fixed string like 'on'.

  3. 3

    For a group of checkboxes, how do you store which ones are selected?

    Why: A group lets several boxes be on at once, so you keep the selected values in an array. You add a value when its box is ticked and remove it when unticked.

  4. 4

    When a checkbox in a group is unticked, how do you update the array?

    Why: filter returns a new array without that value, leaving the original untouched. push and splice mutate the same array, so React may not re-render.

🚀 What’s Next?

You can now handle checkboxes on their own, both a single switch and a whole group. But real forms usually have many fields at once, and writing a separate handler for each one gets tiring fast. Next we’ll see how to handle several inputs with a single shared handler.

React Multiple Inputs with One Handler

Share & Connect