How to work with checkboxes in React
Tags: react
Published: 2022-07-20 13:39:06

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.

{% embed https://youtu.be/WwBu3oykkBs %}

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

```jsx
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
```

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.

```jsx
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
```

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:

```jsx
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
```

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](https://www.codingdeft.com/posts/react-controlled-uncontrolled/).

```jsx
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
```

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 (
    <div>
      <input
        type="checkbox"
        id="checkbox"
        checked={isChecked}
        onChange={checkHandler}
      />
      <label htmlFor="checkbox">{label}</label>
    </div>
  )
}

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

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

  return (
    <div className="App">
      <Checkbox
        isChecked={isChecked}
        checkHandler={checkHandler}
        label="I agree to Terms of Service"
      />
      <p>The checkbox is {isChecked ? "checked" : "unchecked"}</p>
    </div>
  )
}

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
```

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.

```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 }) => {
  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
```