Table of Contents
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:
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}910function App() {11 return (12 <div className="App">13 <Checkbox />14 </div>15 )16}1718export 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.
1import { useState } from "react"23export 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}1314function App() {15 return (16 <div className="App">17 <Checkbox />18 </div>19 )20}2122export 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:
1import { useState } from "react"23export const Checkbox = () => {4 const [isChecked, setIsChecked] = useState(false)56 const checkHandler = () => {7 setIsChecked(!isChecked)8 }910 return (11 <div>12 <input13 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}2324function App() {25 return (26 <div className="App">27 <Checkbox />28 </div>29 )30}3132export 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.
1import { useRef } from "react"23function App() {4 const checkboxRef = useRef(null)56 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 <input16 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}2930export 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"23export const Checkbox = ({ isChecked, label, checkHandler }) => {4 return (5 <div>6 <input7 type="checkbox"8 id="checkbox"9 checked={isChecked}10 onChange={checkHandler}11 />12 <label htmlFor="checkbox">{label}</label>13 </div>14 )15}1617function App() {18 const [isChecked, setIsChecked] = useState(false)1920 const checkHandler = () => {21 setIsChecked(!isChecked)22 }2324 return (25 <div className="App">26 <Checkbox27 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}3536export 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:
1import { useState } from "react"23const 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]1112export const Checkbox = ({ isChecked, label, checkHandler, index }) => {13 return (14 <div>15 <input16 type="checkbox"17 id={`checkbox-${index}`}18 checked={isChecked}19 onChange={checkHandler}20 />21 <label htmlFor={`checkbox-${index}`}>{label}</label>22 </div>23 )24}2526function App() {27 const [toppings, setToppings] = useState(allToppings)2829 const updateCheckStatus = index => {30 setToppings(31 toppings.map((topping, currentIndex) =>32 currentIndex === index33 ? { ...topping, checked: !topping.checked }34 : topping35 )36 )3738 // or39 // setToppings([40 // ...toppings.slice(0, index),41 // { ...toppings[index], checked: !toppings[index].checked },42 // ...toppings.slice(index + 1),43 // ]);44 }4546 return (47 <div className="App">48 {toppings.map((topping, index) => (49 <Checkbox50 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}6364export 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.
1import { useState } from "react"23const 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]1112export const Checkbox = ({ isChecked, label, checkHandler, index }) => {13 console.log({ isChecked })14 return (15 <div>16 <input17 type="checkbox"18 id={`checkbox-${index}`}19 checked={isChecked}20 onChange={checkHandler}21 />22 <label htmlFor={`checkbox-${index}`}>{label}</label>23 </div>24 )25}2627function App() {28 const [toppings, setToppings] = useState(allToppings)2930 const updateCheckStatus = index => {31 setToppings(32 toppings.map((topping, currentIndex) =>33 currentIndex === index34 ? { ...topping, checked: !topping.checked }35 : topping36 )37 )3839 // or40 // setToppings([41 // ...toppings.slice(0, index),42 // { ...toppings[index], checked: !toppings[index].checked },43 // ...toppings.slice(index + 1),44 // ]);45 }4647 const selectAll = () => {48 setToppings(toppings.map(topping => ({ ...topping, checked: true })))49 }50 const unSelectAll = () => {51 setToppings(toppings.map(topping => ({ ...topping, checked: false })))52 }5354 return (55 <div className="App">56 <p>57 <button onClick={selectAll}>Select All</button>58 <button onClick={unSelectAll}>Unselect All</button>59 </p>6061 {toppings.map((topping, index) => (62 <Checkbox63 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}7677export default App
Do follow me on twitter where I post developer insights more often!