Working With Controlled vs. Uncontrolled Checkboxes in React

When you dive into React, checkboxes seem easy at first. You think it’s just a little box that switches between true and false. Then you hit a snag. Maybe the checkbox doesn’t update right away. Or maybe it’s not keeping the right state.
That’s when you realize there’s more to checkboxes than just clicking them. You’ll come across two terms: controlled and uncontrolled. Understanding the difference can save you so much time when it comes to debugging.
Checkboxes Look Simple, But They Aren’t Always
It’s easy to throw a checkbox into your form and assume it works. But once your project grows, things get messy. Especially if you’re tracking user inputs. That’s when you start looking into the best way to handle checkboxes in React.
At some point, you’ll start searching online and come across examples showing how to build a checkbox in React using different methods. Some use useState, others use ref, and you’re left wondering what’s better.
Here’s the thing—both controlled and uncontrolled checkboxes work. But they behave differently. One gives you full control over the checkbox state. The other kind of just does its own thing.
What Is a Controlled Checkbox?
A controlled checkbox in React means the checkbox value is tied to component state. You control it with a state hook like useState. Every time the user clicks the checkbox, you update the state. And that state tells the checkbox if it should be checked or not.
Here’s a super basic example:
const [isChecked, setIsChecked] = useState(false);
<input
type="checkbox"
checked={isChecked}
onChange={(e) => setIsChecked(e.target.checked)}
/>
With this setup, the checkbox is always in sync with your app. If something changes the state elsewhere, the checkbox reacts to it. That’s the benefit. You know exactly what’s going on. You can even pass that value to a parent component or save it to a form.
Uncontrolled Checkboxes Let the DOM Do the Work
Uncontrolled checkboxes are different. Instead of tracking state in your component, you let the DOM handle it. You don’t need useState. Instead, you use a ref to access the checkbox when needed.
Example:
const checkboxRef = useRef();
<input type="checkbox" ref={checkboxRef} defaultChecked={false} />
To check the value later, you’d read it like this:
const isChecked = checkboxRef.current.checked;
This works if you don’t need to constantly update or watch the value. If you just want to read it once, like when someone submits a form, it’s fine. But it can get tricky when your UI needs to reflect the current value live.
So Which One Should You Use?
If you want tight control over the checkbox state, use controlled components. They work better in big apps. They make things easier to manage when you have complex forms. You always know what the checkbox is doing.
But if you have a really simple use case, like a quick settings toggle or a one-time value read, uncontrolled might be easier. It’s less code. It’s also slightly better for performance, since React isn’t re-rendering every time the checkbox changes.
Still, most of the time, controlled checkboxes are safer. They play nicer with other React stuff.
Mixing Controlled and Uncontrolled? Not a Great Idea
Sometimes developers mix both methods. Maybe they set defaultChecked and also try to use checked. That can cause bugs. React will yell at you in the console. It doesn’t like it when you switch between controlled and uncontrolled.
Pick one style and stick with it. If you go with useState, don’t use defaultChecked. Use checked. If you’re using a ref, don’t try to also control the value through props. That’s where things break.
Why Does This Matter for Real Projects?
When you build real-world apps, you’ll have forms, toggles, filters, and lots of checkboxes. You’ll want to test your forms. You’ll want to store checkbox states. Maybe even send those states to an API. Controlled checkboxes give you that power.
They let you track every change. They keep your UI and data in sync. If a user edits something and comes back later, your form can remember what they picked. That kind of stuff matters.
And let’s be honest—bugs with checkboxes are annoying. They feel like they should just work. But if you’re not using the right pattern, you’ll chase down weird issues. That’s why it helps to pick the right approach early.
Wrapping It Up
Controlled and uncontrolled checkboxes may seem like a small detail. But they matter when your app grows. Controlled checkboxes give you full control. They let your app stay in sync. Uncontrolled ones are faster and lighter, but they don’t offer much flexibility.
If you’re learning React or building something serious, go with controlled. If you’re building something simple, uncontrolled is okay. Just don’t mix them. And remember, no checkbox is as simple as it looks—until you learn how to handle it the React way.