Skip to content
react

How to work with checkboxes in React

Apr 1, 2023Abhishek EH6 Min Read
How to work with checkboxes in React

You might have come across multiple instances where you will have to use checkboxes like agreeing to terms and conditions, selecting a list of preferences, etc. In this article, we will learn different scenarios of using checkboxes in React.

First, let's create a simple checkbox component as shown below:

App.js
1export const Checkbox = () => {
2 return (
3 <div>
4 <input type="checkbox" id="checkbox" />
5 <label htmlFor="checkbox">I agree to Terms of Service </label>
6 </div>
7 )
8}
9
10function App() {
11 return (
12 <div className="App">
13 <Checkbox />
14 </div>
15 )
16}
17
18export default App

Now if you test the application, you will see that you can check and uncheck the checkbox. But how do we know the current state of the checkbox?

Storing and Reading the checkbox state

We can make use of the useState hook to store the state of the checkbox.

App.js
1import { useState } from "react"
2
3export const Checkbox = () => {
4 const [isChecked, setIsChecked] = useState(false)
5 return (
6 <div>
7 <input type="checkbox" id="checkbox" checked={isChecked} />
8 <label htmlFor="checkbox">I agree to Terms of Service </label>
9 <p>The checkbox is {isChecked ? "checked" : "unchecked"}</p>
10 </div>
11 )
12}
13
14function App() {
15 return (
16 <div className="App">
17 <Checkbox />
18 </div>
19 )
20}
21
22export default App

Now if you try to check the checkbox, nothing would happen and you will see the following warning in the console:

You provided a checked prop to a form field without an onChange handler. This will render a read-only field. If the field should be mutable use defaultChecked. Otherwise, set either onChange or readOnly.

Why does this happen? As the warning suggests, we are just setting the value of the state to the checkbox and not doing anything while the checkbox state changes. So let's bind an on change handler:

App.js
1import { useState } from "react"
2
3export const Checkbox = () => {
4 const [isChecked, setIsChecked] = useState(false)
5
6 const checkHandler = () => {
7 setIsChecked(!isChecked)
8 }
9
10 return (
11 <div>
12 <input
13 type="checkbox"
14 id="checkbox"
15 checked={isChecked}
16 onChange={checkHandler}
17 />
18 <label htmlFor="checkbox">I agree to Terms of Service </label>
19 <p>The checkbox is {isChecked ? "checked" : "unchecked"}</p>
20 </div>
21 )
22}
23
24function App() {
25 return (
26 <div className="App">
27 <Checkbox />
28 </div>
29 )
30}
31
32export default App

If you want the checkbox to be checked initially, then you can pass true to the useState hook while initializing it.

Checkbox using uncontrolled input

The above example we have seen is using controlled inputs. Next, we will see how to implement the same using uncontrolled inputs. I have explained the difference between controlled and uncontrolled inputs in my previous article.

App.js
1import { useRef } from "react"
2
3function App() {
4 const checkboxRef = useRef(null)
5
6 const formSubmitHandler = e => {
7 e.preventDefault()
8 alert(
9 `The checkbox is ${checkboxRef.current.checked ? "checked" : "unchecked"}`
10 )
11 }
12 return (
13 <div className="App">
14 <form onSubmit={formSubmitHandler}>
15 <input
16 type="checkbox"
17 id="checkbox"
18 defaultChecked={true}
19 ref={checkboxRef}
20 />
21 <label htmlFor="checkbox">I agree to Terms of Service </label>
22 <p>
23 <button type="submit">Submit</button>
24 </p>
25 </form>
26 </div>
27 )
28}
29
30export default App

Here we are providing the initial value using defaultChecked prop. We have created a reference to the checkbox so that we can access the value of the checkbox inside the form submit handler. Here we are not using any state to store the current state of the checkbox. It is stored in the DOM itself.

It is always recommended to use controlled components over uncontrolled components because as the name suggests, we have more control over the input.

Reusing the checkbox component for displaying multiple checkboxes

First, let's make the checkbox component that we created earlier as a reusable component:

1import { useState } from "react"
2
3export const Checkbox = ({ isChecked, label, checkHandler }) => {
4 return (
5 <div>
6 <input
7 type="checkbox"
8 id="checkbox"
9 checked={isChecked}
10 onChange={checkHandler}
11 />
12 <label htmlFor="checkbox">{label}</label>
13 </div>
14 )
15}
16
17function App() {
18 const [isChecked, setIsChecked] = useState(false)
19
20 const checkHandler = () => {
21 setIsChecked(!isChecked)
22 }
23
24 return (
25 <div className="App">
26 <Checkbox
27 isChecked={isChecked}
28 checkHandler={checkHandler}
29 label="I agree to Terms of Service"
30 />
31 <p>The checkbox is {isChecked ? "checked" : "unchecked"}</p>
32 </div>
33 )
34}
35
36export default App

Now let's say you have a use case where you want to display a list of pizza toppings, from which you want the users to choose. We can achieve that by the following code:

App.js
1import { useState } from "react"
2
3const allToppings = [
4 { name: "Golden Corn", checked: false },
5 { name: "Paneer", checked: false },
6 { name: "Tomato", checked: false },
7 { name: "Mushroom", checked: false },
8 { name: "Onion", checked: false },
9 { name: "Black Olives", checked: false },
10]
11
12export const Checkbox = ({ isChecked, label, checkHandler, index }) => {
13 return (
14 <div>
15 <input
16 type="checkbox"
17 id={`checkbox-${index}`}
18 checked={isChecked}
19 onChange={checkHandler}
20 />
21 <label htmlFor={`checkbox-${index}`}>{label}</label>
22 </div>
23 )
24}
25
26function App() {
27 const [toppings, setToppings] = useState(allToppings)
28
29 const updateCheckStatus = index => {
30 setToppings(
31 toppings.map((topping, currentIndex) =>
32 currentIndex === index
33 ? { ...topping, checked: !topping.checked }
34 : topping
35 )
36 )
37
38 // or
39 // setToppings([
40 // ...toppings.slice(0, index),
41 // { ...toppings[index], checked: !toppings[index].checked },
42 // ...toppings.slice(index + 1),
43 // ]);
44 }
45
46 return (
47 <div className="App">
48 {toppings.map((topping, index) => (
49 <Checkbox
50 key={topping.name}
51 isChecked={topping.checked}
52 checkHandler={() => updateCheckStatus(index)}
53 label={topping.name}
54 index={index}
55 />
56 ))}
57 <p>
58 <pre>{JSON.stringify(toppings, null, 2)}</pre>
59 </p>
60 </div>
61 )
62}
63
64export default App

Here we are storing the check status of the checkbox in the local state toppings. We have written a method updateCheckStatus, which will be called with the index of the changed checkbox and will update the local state. We are also displaying the current state in the JSON format so that we can verify everything is working as expected.

Select All and Unselect All

We can implement select all and unselect all by simply updating all the checked statuses to true and false respectively.

App.js
1import { useState } from "react"
2
3const allToppings = [
4 { name: "Golden Corn", checked: false },
5 { name: "Paneer", checked: false },
6 { name: "Tomato", checked: false },
7 { name: "Mushroom", checked: false },
8 { name: "Onion", checked: false },
9 { name: "Black Olives", checked: false },
10]
11
12export const Checkbox = ({ isChecked, label, checkHandler, index }) => {
13 console.log({ isChecked })
14 return (
15 <div>
16 <input
17 type="checkbox"
18 id={`checkbox-${index}`}
19 checked={isChecked}
20 onChange={checkHandler}
21 />
22 <label htmlFor={`checkbox-${index}`}>{label}</label>
23 </div>
24 )
25}
26
27function App() {
28 const [toppings, setToppings] = useState(allToppings)
29
30 const updateCheckStatus = index => {
31 setToppings(
32 toppings.map((topping, currentIndex) =>
33 currentIndex === index
34 ? { ...topping, checked: !topping.checked }
35 : topping
36 )
37 )
38
39 // or
40 // setToppings([
41 // ...toppings.slice(0, index),
42 // { ...toppings[index], checked: !toppings[index].checked },
43 // ...toppings.slice(index + 1),
44 // ]);
45 }
46
47 const selectAll = () => {
48 setToppings(toppings.map(topping => ({ ...topping, checked: true })))
49 }
50 const unSelectAll = () => {
51 setToppings(toppings.map(topping => ({ ...topping, checked: false })))
52 }
53
54 return (
55 <div className="App">
56 <p>
57 <button onClick={selectAll}>Select All</button>
58 <button onClick={unSelectAll}>Unselect All</button>
59 </p>
60
61 {toppings.map((topping, index) => (
62 <Checkbox
63 key={topping.name}
64 isChecked={topping.checked}
65 checkHandler={() => updateCheckStatus(index)}
66 label={topping.name}
67 index={index}
68 />
69 ))}
70 <p>
71 <pre>{JSON.stringify(toppings, null, 2)}</pre>
72 </p>
73 </div>
74 )
75}
76
77export default App

Do follow me on twitter where I post developer insights more often!

© 2024 CodingDeft.Com