DEV Community

Cover image for How to work with checkboxes in React
collegewap
collegewap

Posted on • Updated on • Originally published at codingdeft.com

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:

export const Checkbox = () => {
  return (
    <div>
      <input type="checkbox" id="checkbox" />
      <label htmlFor="checkbox">I agree to Terms of Service </label>
    </div>
  )
}

function App() {
  return (
    <div className="App">
      <Checkbox />
    </div>
  )
}

export default App
Enter fullscreen mode Exit fullscreen mode

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.

import { useState } from "react"

export const Checkbox = () => {
  const [isChecked, setIsChecked] = useState(false)
  return (
    <div>
      <input type="checkbox" id="checkbox" checked={isChecked} />
      <label htmlFor="checkbox">I agree to Terms of Service </label>
      <p>The checkbox is {isChecked ? "checked" : "unchecked"}</p>
    </div>
  )
}

function App() {
  return (
    <div className="App">
      <Checkbox />
    </div>
  )
}

export default App
Enter fullscreen mode Exit fullscreen mode

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:

import { useState } from "react"

export const Checkbox = () => {
  const [isChecked, setIsChecked] = useState(false)

  const checkHandler = () => {
    setIsChecked(!isChecked)
  }

  return (
    <div>
      <input
        type="checkbox"
        id="checkbox"
        checked={isChecked}
        onChange={checkHandler}
      />
      <label htmlFor="checkbox">I agree to Terms of Service </label>
      <p>The checkbox is {isChecked ? "checked" : "unchecked"}</p>
    </div>
  )
}

function App() {
  return (
    <div className="App">
      <Checkbox />
    </div>
  )
}

export default App
Enter fullscreen mode Exit fullscreen mode

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.

import { useRef } from "react"

function App() {
  const checkboxRef = useRef(null)

  const formSubmitHandler = e => {
    e.preventDefault()
    alert(
      `The checkbox is ${checkboxRef.current.checked ? "checked" : "unchecked"}`
    )
  }
  return (
    <div className="App">
      <form onSubmit={formSubmitHandler}>
        <input
          type="checkbox"
          id="checkbox"
          defaultChecked={true}
          ref={checkboxRef}
        />
        <label htmlFor="checkbox">I agree to Terms of Service </label>
        <p>
          <button type="submit">Submit</button>
        </p>
      </form>
    </div>
  )
}

export default App
Enter fullscreen mode Exit fullscreen mode

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:

```jsx App.js
import { useState } from "react"

export const Checkbox = ({ isChecked, label, checkHandler }) => {
return (


type="checkbox"
id="checkbox"
checked={isChecked}
onChange={checkHandler}
/>
{label}

)
}

function App() {
const [isChecked, setIsChecked] = useState(false)

const checkHandler = () => {
setIsChecked(!isChecked)
}

return (


isChecked={isChecked}
checkHandler={checkHandler}
label="I agree to Terms of Service"
/>

The checkbox is {isChecked ? "checked" : "unchecked"}



)
}

export 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:



```jsx
import { useState } from "react"

const allToppings = [
  { name: "Golden Corn", checked: false },
  { name: "Paneer", checked: false },
  { name: "Tomato", checked: false },
  { name: "Mushroom", checked: false },
  { name: "Onion", checked: false },
  { name: "Black Olives", checked: false },
]

export const Checkbox = ({ isChecked, label, checkHandler, index }) => {
  return (
    <div>
      <input
        type="checkbox"
        id={`checkbox-${index}`}
        checked={isChecked}
        onChange={checkHandler}
      />
      <label htmlFor={`checkbox-${index}`}>{label}</label>
    </div>
  )
}

function App() {
  const [toppings, setToppings] = useState(allToppings)

  const updateCheckStatus = index => {
    setToppings(
      toppings.map((topping, currentIndex) =>
        currentIndex === index
          ? { ...topping, checked: !topping.checked }
          : topping
      )
    )

    // or
    // setToppings([
    //   ...toppings.slice(0, index),
    //   { ...toppings[index], checked: !toppings[index].checked },
    //   ...toppings.slice(index + 1),
    // ]);
  }

  return (
    <div className="App">
      {toppings.map((topping, index) => (
        <Checkbox
          key={topping.name}
          isChecked={topping.checked}
          checkHandler={() => updateCheckStatus(index)}
          label={topping.name}
          index={index}
        />
      ))}
      <p>
        <pre>{JSON.stringify(toppings, null, 2)}</pre>
      </p>
    </div>
  )
}

export default App
Enter fullscreen mode Exit fullscreen mode

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.

import { useState } from "react"

const allToppings = [
  { name: "Golden Corn", checked: false },
  { name: "Paneer", checked: false },
  { name: "Tomato", checked: false },
  { name: "Mushroom", checked: false },
  { name: "Onion", checked: false },
  { name: "Black Olives", checked: false },
]

export const Checkbox = ({ isChecked, label, checkHandler, index }) => {
  console.log({ isChecked })
  return (
    <div>
      <input
        type="checkbox"
        id={`checkbox-${index}`}
        checked={isChecked}
        onChange={checkHandler}
      />
      <label htmlFor={`checkbox-${index}`}>{label}</label>
    </div>
  )
}

function App() {
  const [toppings, setToppings] = useState(allToppings)

  const updateCheckStatus = index => {
    setToppings(
      toppings.map((topping, currentIndex) =>
        currentIndex === index
          ? { ...topping, checked: !topping.checked }
          : topping
      )
    )

    // or
    // setToppings([
    //   ...toppings.slice(0, index),
    //   { ...toppings[index], checked: !toppings[index].checked },
    //   ...toppings.slice(index + 1),
    // ]);
  }

  const selectAll = () => {
    setToppings(toppings.map(topping => ({ ...topping, checked: true })))
  }
  const unSelectAll = () => {
    setToppings(toppings.map(topping => ({ ...topping, checked: false })))
  }

  return (
    <div className="App">
      <p>
        <button onClick={selectAll}>Select All</button>
        <button onClick={unSelectAll}>Unselect All</button>
      </p>

      {toppings.map((topping, index) => (
        <Checkbox
          key={topping.name}
          isChecked={topping.checked}
          checkHandler={() => updateCheckStatus(index)}
          label={topping.name}
          index={index}
        />
      ))}
      <p>
        <pre>{JSON.stringify(toppings, null, 2)}</pre>
      </p>
    </div>
  )
}

export default App
Enter fullscreen mode Exit fullscreen mode

Top comments (0)