Using a indeterminate React Checkbox

 by Robin Wieruch
 - Edit this Post

This tutorial is part 2 of 2 in this series.

A short React tutorial by example for beginners on how to create an indeterminate React Checkbox which uses an indeterminate state (also called tri state).

Let's start with a checkbox example from our previous tutorial:

const App = () => {
const [checked, setChecked] = React.useState(false);
const handleChange = () => {
setChecked(!checked);
};
return (
<div>
<Checkbox
label="Value"
value={checked}
onChange={handleChange}
/>
<p>Is checked? {checked.toString()}</p>
</div>
);
};
const Checkbox = ({ label, value, onChange }) => {
return (
<label>
<input type="checkbox" checked={value} onChange={onChange} />
{label}
</label>
);
};

Now we want to extend the functionality of this checkbox for handling a tri state instead of a bi state. First, we need to transform our state from a boolean to an enum, because only this way we can create a tri state:

const CHECKBOX_STATES = {
Checked: 'Checked',
Indeterminate: 'Indeterminate',
Empty: 'Empty',
};
const App = () => {
const [checked, setChecked] = React.useState(CHECKBOX_STATES.Empty);
const handleChange = () => {
let updatedChecked;
if (checked === CHECKBOX_STATES.Checked) {
updatedChecked = CHECKBOX_STATES.Empty;
} else if (checked === CHECKBOX_STATES.Empty) {
updatedChecked = CHECKBOX_STATES.Checked;
}
setChecked(updatedChecked);
};
return (
<div>
<Checkbox
label="Value"
value={checked}
onChange={handleChange}
/>
<p>Is checked? {checked}</p>
</div>
);
};
const Checkbox = ({ label, value, onChange }) => {
return (
<label>
<input
type="checkbox"
checked={value === CHECKBOX_STATES.Checked}
onChange={onChange}
/>
{label}
</label>
);
};

We have the same behavior as before, but enabled us to have more than two states for our checkbox.

Next comes the indeterminate state of a checkbox. Unfortunately it cannot be assigned via HTML and we need to use an imperative DOM manipulation here. Fortunately React has the concept of which gives React developers access to DOM elements:

const Checkbox = ({ label, value, onChange }) => {
const checkboxRef = React.useRef();
return (
<label>
<input
ref={checkboxRef}
type="checkbox"
checked={value === CHECKBOX_STATES.Checked}
onChange={onChange}
/>
{label}
</label>
);
};

By having access to the checkbox element, we can set and unset the checked state imperatively instead of using the HTML in a declarative way:

const Checkbox = ({ label, value, onChange }) => {
const checkboxRef = React.useRef();
React.useEffect(() => {
if (value === CHECKBOX_STATES.Checked) {
checkboxRef.current.checked = true;
} else {
checkboxRef.current.checked = false;
}
}, [value]);
return (
<label>
<input ref={checkboxRef} type="checkbox" onChange={onChange} />
{label}
</label>
);
};

executes its passed side-effect function every time a variable in the dependency array (here: value) changes. Then in the side-effect function we evaluate the value: if it is checked, we set the checkbox's internal HTML state programmatically to checked; and vice versa for the unchecked state.

Finally, we can assign the indeterminate state this way too:

const Checkbox = ({ label, value, onChange }) => {
const checkboxRef = React.useRef();
React.useEffect(() => {
if (value === CHECKBOX_STATES.Checked) {
checkboxRef.current.checked = true;
checkboxRef.current.indeterminate = false;
} else if (value === CHECKBOX_STATES.Empty) {
checkboxRef.current.checked = false;
checkboxRef.current.indeterminate = false;
} else if (value === CHECKBOX_STATES.Indeterminate) {
checkboxRef.current.checked = false;
checkboxRef.current.indeterminate = true;
}
}, [value]);
return (
<label>
<input ref={checkboxRef} type="checkbox" onChange={onChange} />
{label}
</label>
);
};

And don't forget to assign the proper value on state change in the first place:

const App = () => {
const [checked, setChecked] = React.useState(CHECKBOX_STATES.Empty);
const handleChange = () => {
let updatedChecked;
if (checked === CHECKBOX_STATES.Checked) {
updatedChecked = CHECKBOX_STATES.Empty;
} else if (checked === CHECKBOX_STATES.Empty) {
updatedChecked = CHECKBOX_STATES.Indeterminate;
} else if (checked === CHECKBOX_STATES.Indeterminate) {
updatedChecked = CHECKBOX_STATES.Checked;
}
setChecked(updatedChecked);
};
return (
<div>
<Checkbox
label="Value"
value={checked}
onChange={handleChange}
/>
<p>Is checked? {checked}</p>
</div>
);
};

That's it. We transformed our React checkbox component from a bi state to a tri state by introducing the indeterminate state. I hope this tutorial is useful to you if you happen to need a checkbox with three states.

Keep reading about 

A short React tutorial by example for beginners about using a checkbox in React. First of all, a checkbox is just an HTML input field with the type of checkbox which can be rendered in React's JSX…

The Road to React

Learn React by building real world applications. No setup configuration. No tooling. Plain React in 200+ pages of learning material. Learn React like 50.000+ readers.

Get it on Amazon.