In this blog post, we will look at:
- Uncontrolled checkbox components
- Controlled checkbox components
I prefer to use controlled checkbox components - but to understand why, let's start with an uncontrolled checkbox.
Uncontrolled checkbox component
The first (and possibly simplest) way to check and uncheck a checkbox in React is with an uncontrolled checkbox component. And this simply means, React doesn't control it - the checkbox is handled by the DOM (like in the olden days!).
Because forms (and checkboxes) existed before React, you don't need React to handle them - if you don't want.
// checkbox
function Checkbox() {
return (
<div>
<input type="checkbox" id="one" name="one" />
<label for="one">Checkbox 1</label>
</div>
);
};
Go ahead, use the checkbox below, and click it a few times. It works!
<div id="app"></div>
// checkbox component function Checkbox() { return ( <div className="p-10 flex justify-center items-center h-screen"> <input type="checkbox" id="one" name="one"/> <label for="one" className="ml-2 font-bold">Uncontrolled Checkbox</label> </div> ); }; // render react ReactDOM.render( <Checkbox />, document.getElementById('app') );
Yeah, ok that works, but what if I want the checkbox checked to start with (instead of unchecked)?
React checkbox with defaultChecked
No problem. Just pass a defaultChecked={true}
prop.
But let's not hardcode that in, since we might want a couple of checkboxes, one checked and one unchecked to start us off.
You can pass defaultChecked
as a prop to the component, and then decide what checkboxes you want checked.
And while we are at it, let's pass the id
and name
as props so we can reuse the Checkbox component for multiple checkboxes. And we can use the children
prop for the label.
// checkbox
function Checkbox({ id, name, defaultChecked, children }) {
return (
<div>
<input type="checkbox" id={id} name={name} defaultChecked={defaultChecked} />
<label for={id}>{children}</label>
</div>
);
};
Now we can use our checkbox like this:
<Checkbox id="example-checked" name="checked-checkbox" defaultChecked={true}>Default Checked</Checkbox>
<Checkbox id="example-unchecked" name="unchecked-checkbox" defaultChecked={false}>Default Unchecked</Checkbox>
Make sure you give each Checkbox
a unique id.
Is the uncontrolled checkbox checked or unchecked?
Since this checkbox is uncontrolled, once the user has clicked it a few times, how do we know if it is checked or unchecked?
Let's say a user clicked a submit button on a form, and we want to let the server know if the checkbox was checked or not - how do we do it?
const checkboxRef = React.useRef(null);
function onSubmit() {
console.log(checkboxRef.current.checked);
};
return (
<form onSubmit={onSubmit}>
<input ref={checkboxRef} type="checkbox" id="one" name="one" defaultChecked={true} />
<label for="one">Checkbox 1</label>
</div>
);
Why might want a controlled checkbox instead
So far we have looked at examples of an uncontrolled checkbox in React. And it works!
But an uncontrolled checkbox in React does come with drawbacks. While this checkbox works, and it's all good like it should, it's not very well integrated with React.
- What if you want something to happen when the user checks the box?
- What if you want to know when the user checks the box?
- What if you need to validate the form data (maybe with other form elements)?
- What if you want to check the box for them?*
*You could do this with the ref
and checkboxRef.current.click();
but I'd argue that state is a better option when combined with any of the other needs listed above.
So let's look at how we can useState
to create a controlled checkbox component instead!
Controlled checkbox component
So we made our uncontrolled
checkbox component, but we didn't know when the checkbox was checked, and when it was unchecked. Let's give React (and ourselves!) a little more control over things!
To create a controlled checkbox, we need to give it some state, and it's the state (not the DOM) that tells the checkbox if it's checked - or not.
// controlled checkbox
function ControlledCheckbox({ id, name, children, defaultChecked = false }) {
// state
const [checked, setChecked] = React.useState(defaultChecked);
return (
<div className="my-5">
<input type="checkbox" id={id} name={name} checked={checked} onChange={() => setChecked(!checked)} />
<label for={id} className="ml-1">{children}</label>
</div>
);
};
We now useState
for our checked
state. And pass both checked
and an onChange
handler to the checkbox input.
<input type="checkbox"
id={id}
name={name}
checked={checked} // if the checkbox is checked (or not)
onChange={() => setChecked(!checked)} // toggles the checked state
/>
The onChange
handler calls a function that will update the state to !checked
. If checked
is true
, this will set it to false
, and vice versa.
Notice that we don't pass defaultChecked
to the input
element anymore. Since the checked
state handles if the checkbox is checked, we can set defaultChecked
as the initial value for checked to achieve the same thing.
Here's the controlled checkbox in action:
<div id="app"></div>
// checkbox component function Checkbox({ id, name, children, defaultChecked = false }) { // state const [checked, setChecked] = React.useState(defaultChecked); // the checkbox return ( <div className="p-10 flex justify-center items-center h-screen"> <input type="checkbox" id={id} name={name} checked={checked} onChange={() => setChecked(!checked)}/> <label for={id} className="ml-2 font-bold">{children}</label> </div> ); }; // render react ReactDOM.render( <Checkbox id="controlled-one" name="controlled-checkbox" defaultChecked={true}>Controlled Checkbox</Checkbox>, document.getElementById('app') );
Ok, so what's the benefit here? We could already check and uncheck the checkbox before with the uncontrolled component. So why do we need a controlled one?
Well, here's the magic. 🪄
Whenever the state updates, the component re-renders. And that means that React now knows when the component is checked, and can do things, like focus on a new element, show a hidden field, or just display a bit of text.
You can also do things like build a select all checkbox, which I've also written a blog about!
In this example, I'll just show a "checked" message at the end of the label with {checked && "(checked!)"}
- try doing that with an uncontrolled checkbox!
// controlled checkbox
function ControlledCheckbox({ id, name, children, defaultChecked = false }) {
// state
const [checked, setChecked] = React.useState(defaultChecked);
return (
<div className="my-5">
<input type="checkbox" id={id} name={name} checked={checked} onChange={() => setChecked(!checked)} />
<label for={id} className="ml-1">{children} {checked && "(checked!)"}</label>
</div>
);
};
Or you could something, like make the label grey if the checkbox isn't checked.
<div id="app"></div>
// checkbox component function Checkbox({ id, name, children, defaultChecked = false }) { // state const [checked, setChecked] = React.useState(defaultChecked); // the checkbox return ( <div className="p-10 flex justify-center items-center h-screen"> <input type="checkbox" id={id} name={name} checked={checked} onChange={() => setChecked(!checked)}/> <label for={id} className={`ml-2 font-bold ${checked ? "text-black" : "text-gray-500"}`}>{children}</label> </div> ); }; // render react ReactDOM.render( <Checkbox id="controlled" name="controlled-checkbox">Controlled Checkbox</Checkbox>, document.getElementById('app') );
Personally, I prefer using a controlled checkbox, because you get that instant knowledge when the checkbox is checked. Let's say, when a user ticks the checkbox, you want to run some action like signing them up to a mailing list or updating their preferences, you can run a useEffect
when the checked
value changes.
React.useEffect(() => {
if (checked) {
// do something
}
}, [checked]);
Three cheers for controlled components!
If you're having any trouble getting your checkbox onChange
to check your checkbox, or it needs two clicks - check out my blog post on React checkbox onChange not firing/updating the first time for problem-solving tips!