69

I am having a very annoying issue with React and checkboxes. The application I am working with requires a list of checkboxes that represent settings that are persisted in the back-end. There is an option to restore the settings to their original state.

At first, I created a component that has an object like a map of settings. Each setting has a key and a boolean value. Hence:

{
    bubbles: true,
    gregory: false
}

Is to be represented as:

<input type="checkbox" value="bubbles" checked="checked" />
<input type="checkbox" value="gregory" />

Now, it seems React is ignorant about how a checkbox is supposed to work. I don't want to set the checkboxes' values, I want the "checked" property.

Yet, if I try something like this:

<input
    type="checkbox"
    value={setting}
    checked={this.settings[setting]}
    onChange={this.onChangeAction.bind(this)}
/>

I get this warning:

Warning: AwesomeComponent is changing an uncontrolled input of type checkbox to be controlled. Input elements should not switch from uncontrolled to controlled (or vice versa). Decide between using a controlled or uncontrolled input element for the lifetime of the component. More info: [some useless docs page I read several times to no avail]

So I decided to create another component to wrap each individual checkbox and I got this:

<input
    type="checkbox"
    checked={this.state.checked}
    onChange={this.onChangeAction.bind(this)}
/>

Now the checked is a property present directly in my state.

This yields the same warning, so I tried using defaultChecked:

<input
    type="checkbox"
    defaultChecked={this.state.checked}
    onChange={this.onChangeAction.bind(this)}
/>

Which makes the warning disappear, but now it is unable to reset the checked value to the default one. So I tried playing with the method componentWillReceiveProps, this way I am quite sure my state is correct, this.state.checked is correct and the component renders again.

And it does. But the checkbox remains as it was originally. For now I left that ugly warning and I am using checked. How do I fix this thing so the warning goes away?

I was thinking that perhaps there is a way to force-re-render the component, so it captures the new defaultChecked value and uses it. But I don't know how to do that. Perhaps suppress the warning only for this component? Is that possible? Perhaps there is something else that can be done?

7 Answers 7

113

The problem arises if you set the checked property of your checkbox to null or undefined.

Those are "falsy" values in JS. However, React treats a value of null as if the property was not set at all. Since the default state of a checkbox is unchecked, everything will work fine though. If you then set checked to true, React thinks the property suddenly comes into existence! This is when React figures you switched from uncontrolled to controlled, since now the prop checked exists.

In your example, you can get rid of this warning by changing checked={this.settings[setting]} to checked={!!this.settings[setting]}. Notice the double bang (!!). They will convert null or undefined to false (and leave true alone), so React will register your checked property with a value of false and start off with a controlled form component.

I had this problem too and I, too, read the docs about controlled-components several times to no avail, but I finally figured it out, so I thought I'd share. Also, since version 15.2.0, normal inputs are triggered to be controlled by setting value, while checkboxes are initialized as controlled by setting checked, regardless of their value property, which added a bit to the confusion.

4
  • 3
    Over an hour wasted because I thought I understood the issue due to a very specific warning message from React. Nope. It was an undefined variable becoming defined later. Thank you.
    – Don
    Commented Oct 19, 2016 at 22:25
  • 2
    I cannot thumbs-up this answer enough. I was conditionally adding and removing the checked attribute itself, which was good practice back in the plain-jane HTML days. I spent twenty minutes puzzled as to why this error message was occurring. The ReactJS error message really should have a special case acknowledging the inconsistent weirdness that is the HTML checkbox input so that others don't bump into this as well. Commented May 11, 2017 at 21:41
  • 1
    Incredible answer :) And that's a nice trick that I didn't know about (using the double bang to instantly cast to a boolean value) Commented Jan 9, 2018 at 15:53
  • I also didn't realize that checkboxes are controlled by the 'checked' attribute and not 'value'. I was getting some weird behavior until I found this post. Thank you! reactjs.org/docs/forms.html
    – HaulinOats
    Commented Jul 28, 2021 at 22:13
17

Amoebe's answer is correct, but I think there's a cleaner solution than the double bank (!!). Simply add a defaultProps property with value false for checked prop of your Checkbox component:

import React from 'react';

const Checkbox = ({checked}) => (
  <div>
      <input type="checkbox" checked={checked} />
  </div>
);

Checkbox.defaultProps = {
  checked: false
};

export default Checkbox;
2
  • 2
    How about just {checked = false} in the function declaration?
    – Théophile
    Commented Feb 23, 2021 at 21:37
  • yeah this seems like overkill to me
    – JSON
    Commented Feb 2, 2022 at 17:20
10

Basically, the defaultChecked means you don't want to control the input – it just renders with this value and then there is no way to control it. Also, value shouldn't be used, but checked instead, so your second code should be correct. And you shouldn't use them both simultaneously.

<input
    type="checkbox"
    checked={this.state.checked}
    onChange={this.onChangeAction.bind(this)}
/>

Can you create a small fiddle with this behaviour?

1
  • His problem is because this.state.checked is undefined, rather than false. So something like this is a bit safer: <input type="checkbox" checked={!!this.state.checked} onChange={this.onChangeAction.bind(this)} />
    – JohnFlux
    Commented Sep 11, 2021 at 6:01
5

Here is an answer using hooks should you choose to convert the class component to a functional one...

export default Checklist = () => {
  const [listOfItems, setListOfItems] = useState([
    {name: 'bubbles', isChecked: true},
    {name: 'gregory', isChecked: false}
  ]);

  const updateListOfItems = (itemIndex, isChecked) => {
    const updatedListOfItems = [...listOfItems];
    updatedListOfItems[itemIndex].isChecked = isChecked;
    setListOfItems(updatedListOfItems);
  }

  return (
    {listOfitems.map((item, index) =>
      <index key={index} type="checkbox" checked={item.isChecked} onChange={() => updateListOfItems(index, !item.isChecked)} />
    )}
  )
}
2
  • This helped me but there's typos
    – Jeremy
    Commented Jan 10, 2023 at 14:09
  • 1
    @Jeremy Thanks, I updated the answer to fix the typo. Commented Jan 23, 2023 at 1:30
0

You can assign your data to the state and then make use of the checked property associated with the individual checkbox to set the state as

{   this.state.data.map(function(item, index) {
      return (
        
        <input type="checkbox" checked={item.value} onChange={this.handleChange.bind(this, index)}/>
        
      );
    }.bind(this))
    }

Sample Data in state is as

data: [{name:'bubbles', value: true}, {name:'gregory', value: false}]

JSFIDDLE

0

What finally worked, combining other answers:

const [categories, setCategories] = useState(
    [
        {
            field: 'Label 1',
            checked: true
        },
        {
            field: 'Label 2',
            checked: false
        },
        {
            field: 'Label 3',
            checked: false
        }
    ]
)
const updateList = (item, isChecked) => {
    const updatedList = [...categories];
    updatedList[item].checked = isChecked;
    setCategories(updatedList);
}
... in your markup:
{
  categories.map((item, index) =>
    <div key = {item.field}>
      <label>
        <span>{item.field}</span>
        <input key={index}
               type="checkbox"
               checked={item.checked}
               onChange={() => updateList(index, !item.checked)}
         />
      </label>
    </div>
  )
}
0

event.target can be utilized to set checkbox check property in react

const SomePage=()=>{
  const [isChecked,setChecked ] =useState(false);
  const onChangeEvent=(e)=>{
    setChecked(e.target.checked);
  }

  return(<div>
    <input type="checkbox" checked={isChecked} onChange= 
 {(e) => onChangeEvent(e)} />   
  </div>);
};

Not the answer you're looking for? Browse other questions tagged or ask your own question.