React: Warning is changing a uncontrolled input of type radio to be controlled...

Created on 16 May 2016  ·  30Comments  ·  Source: facebook/react

I have this code

       <input name='test' value={1} type='radio' />
       <input name='test' value={2} type='radio' defaultChecked />

https://jsfiddle.net/69z2wepo/42137/

And when I click on radio it gives me warning
Warning: Test is changing a uncontrolled input of type radio 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: https://fb.me/react-controlled-components

So its not clear why or is it a bug ?

Bug

Most helpful comment

@LiJinyao

It means this.state.isMustFill is not always true or false, but rather true or undefined, for example. Make sure it’s always boolean, and the problem will go away.

All 30 comments

It's a bug. :+1:

I think it's reacting to value being set without considering that it is a radio-input which uses value differently.

I have a related issue, I'm getting a warning that I'm switching an uncontrolled component to a controlled one with this code:

function CheckboxButton({checked, onChange, textComponent} = {}) {
    return (
        <label className='checkbox-button'>
            <input
                type='checkbox'
                checked={checked}
                onChange={onChange}/>
            {textComponent}
        </label>
    );
}

If I add a useless value prop then there is no warning. I think the warning code should acknowledge that the value property of a checkbox does not indicate whether it's controlled or not, but rather the checked prop.

cc @zpao @spicyj sounds like we want to get this fixed.

Yea, I saw this myself yesterday.

This bug is a bit annoying. For one thing, there are many different input types to consider (https://www.w3.org/TR/html5/forms.html#attr-input-type) and browsers seem to support more types than the spec indicates (for instance, Mozilla appears to support "month" as per https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input).

In the particular case of a radio button, this is further complicated by the fact that you can't determine the "controlledness" of a group just by looking at a single input. You need to look at all the inputs with the same name prop within the same form group. Yuck.

Too bad we can't just ban uncontrolled components. That would make life so much easier. :P.

This bug is a bit annoying. For one thing, there are many different input types to consider (https://www.w3.org/TR/html5/forms.html#attr-input-type) and browsers seem to support more types than the spec indicates (for instance, Mozilla appears to support "month" as per https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input).

@jimfb Not sure if I'm following, as far as I understand it and is concerned it's just about checking whether type="radio" or type="checkbox" and then testing checked instead of value, it seems value is being tested regardless right now. The other types shouldn't really matter because they all rely on value.

In the particular case of a radio button, this is further complicated by the fact that you can't determine the "controlledness" of a group just by looking at a single input. You need to look at all the inputs with the same name prop within the same form group. Yuck.

Mixing controlled and uncontrolled radios is a problem too, but an entirely separate problem IMHO. This warning is simply about switching an individual input between being controlled and uncontrolled. I.e. checked should not go from null to non-null or vice versa on an individual component.

Too bad we can't just ban uncontrolled components. That would make life so much easier. :P.

I'm still of the opinion that they should be entirely separate components.

im experiencing this also: clicking the radio shows the error

<div className="radio">
                                        <label>
                                            <input type="radio" name="type" value="In Person" defaultChecked={true} />
                                            <span> In Person</span>
                                        </label>
                                    </div>
                                    <div className="radio margin-left-20">
                                        <label>
                                            <input type="radio" name="type" value="Phone" />
                                            <span> Phone</span>
                                        </label>
                                    </div>
                                    <div className="radio margin-left-20">
                                        <label>
                                            <input type="radio" name="type" value="Webex" />
                                            <span> Webex</span>
                                        </label>
                                    </div>

@syranide I think it's a bit more complicated than that. Think about how you would implement these warnings for switching between controlled and uncontrolled radio buttons.

If any of the radio inputs within that form group have the checked attribute specified, then the whole group is controlled. Controlledness can't be tracked on a per-node basis for radio buttons.

Let's scope this to handling the 90% case which is just that this is warning incorrectly. We can worry about making it perfect some other time.

@zpao That's what I'm talking about - this warning firing incorrectly (too often) for radio buttons.

if we just want a quick fix, I think we need to just disable this warning for radio buttons. I don't think there is a 90% solution for radio buttons that won't occasionally fire spurious warnings. A "correct" fix will require tracking all the radio inputs within each form group.

The test case in the initial report is warning and it should not be. It is not because of needing to track across radios in this case it's because we're incorrectly looking at value for radios and checkboxes (right here: https://github.com/facebook/react/blob/master/src/renderers/dom/client/wrappers/ReactDOMInput.js#L169-L171). We should not be falling back to value, we should be using the type to determine if we should be looking at checked or value.

You are talking about a real problem but it's tangential to this one.

@zpao Right, but an uncontrolled radio group is not required to set a defaultChecked on any of the elements. And a controlled radio group is allowed to set a defaultChecked on a controlled element (for the reset button). This means that unless we do the smarter logic, any check we do is wrong and will fire spurious warnings. We might as well just remove the check (for radio buttons) at that point.

@syranide I think it's a bit more complicated than that. Think about how you would implement these warnings for switching between controlled and uncontrolled radio buttons.

If any of the radio inputs within that form group have the checked attribute specified, then the whole group is controlled. Controlledness can't be tracked on a per-node basis for radio buttons.

@jimfb Huh? Isn't this how you use radio-buttons https://jsfiddle.net/dt03qw4p/? Just like value for text inputs, if checked is null, then it is uncontrolled, if it is non-null then it's controlled and depending on truthiness it's checked or not... right? It doesn't have to check the other radios for this scenario, it just has to check if checked changes from being null to non-null or vice versa (i.e. changing controlledness).

Or what am I missing?

@syranide Are you required to specify checked={false} if the radio button is not checked in a controlled component? Today, the answer is no. Maybe we could/should change that, but it's a much wider discussion and almost certainly not within the scope of a 90% quick fix that @zpao wanted.

Example: this is a legal (immutable) controlled form:

<form>
  <input type="radio" name="foo" value="1" onChange={()=>{}} />
  <input type="radio" name="foo" value="2" onChange={()=>{}} checked />
  <input type="radio" name="foo" value="3" onChange={()=>{}} />
</form>

Obviously it's a contrived example. You can imagine that the user has a list of elements and just "sets" the checked attribute on the one that is checked. But the result is the same.

The "controlledness" of the input is defined by the entire group within the form. The lack of a checked attribute is insufficient to know if a particular input element is controlled. In this case, the controlledness of the first input is determined by the controlledness of the second.

That's why this warning is so tricky for radio buttons.

@syranide Are you required to specify checked={false} if the radio button is not checked in a controlled component? Today, the answer is no.

@jimfb Actually the answer is yes in a sense...? https://jsfiddle.net/hx2r7bc4/ (contrived example, but still)

Also, I would consider it very weird for someone to have a dynamic controlled component and not explicitly set checked (I doubt you'll find anyone that does even), additionally, this is a warning and basically intended to shine light on bad behavior so I'm not really sure if I would consider it a problem anyway?

Example: this is a legal (immutable) controlled form:

It works, I'm not sure if I would agree that it's a good idea. Regardless, that wouldn't be a problem, since checked is not provided a property to the others you can almost be certain that these are static radio buttons and not dynamic. So they would never change controlledness and warn anyway. Further, you should not use that pattern to create static radio buttons, that's what readOnly is for.

So again, this seems like a case of win-win to me, if you're doing it wrong/badly you get a warning. No one doing it right will get warning.

Perhaps I've missed something, but I don't see any case involving good practice that would actually cause you to see the warning, only bad ones.

I withdraw my earlier comment, the spec indicates that value is a required attribute for checkboxes (even if I don't plan to use it)

https://html.spec.whatwg.org/multipage/forms.html#checkbox-state-(type=checkbox)

correction, MDN indicates that it's a required attribute: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/checkbox

The spec doesn't actually seem to say that.. I'm not sure what the citation for MDN is but it seems incorrect to me. Personally I'd rather omit the value property for checkboxes, it seems perfectly sane for the browser to default it to on or something if necessary.

Actually the answer is yes in a sense...? https://jsfiddle.net/hx2r7bc4/ (contrived example, but still)

Wow, that sucks. That is the most terrible thing I've seen all month. But I'll give you credit because it demonstrates a problem with not specifying the checked prop on a controlled component when all values are false.

Also, I would consider it very weird for someone to have a controlled component and not explicitly set checked (I doubt you'll find anyone that does even), additionally, this is a warning and basically intended to shine light on bad behavior so I'm not really sure if I would consider it a problem anyway?

I'm not sure it's that weird. It is pretty intuitive/natural to loop over some data and set checked IFF the data says that value is checked (assuming you aren't using jsx). I'm not even convinced it's bad behavior (except given your fiddle above).

Anyway, I suppose I'm fine with banning that behavior 👍. I think that will work.


The only other edge case I'd worry about is what happens if a controlled checkbox turns into a controlled text input, but that should be easy for us to get right, we just need to test it after making our change(s).

I'm not even convinced it's bad behavior (except given your fiddle above).

Just for posterity. We can't realistically get away from nullness of checked determining the controlledness with the current design. Given that a null checked effectively means uncontrolled I would say that it is wrong to mix them. Now it just so happens that you can rely on that pattern as long as one is always checked, but that is not guaranteed to be true. There may be values used that does not correspond to any radio (not necessarily an error), if you did it the "wrong way" then some radio would still be checked.

The only other edge case I'd worry about is what happens if a controlled checkbox turns into a controlled text input, but that should be easy for us to get right, we just need to test it after making our change(s).

IMHO, I think I had a PR about that that would consider it the same as remounting the component which I think everyone agreed but it never ended up merged. So until something like that happens I wouldn't even worry about it, it's virtually an error in a sense to change the type of an input today (there are other broken edge-cases too AFAIK).

I have the same problem. This is my code:

<input type="checkbox" onChange={this.handlerMustfillChange} checked={this.state.isMustFill}/>

And when I click the checkbox I got the same warning.
warning.js:44 Warning: QuestionHeader is changing an uncontrolled input of type checkbox to be controlled.

@LiJinyao

It means this.state.isMustFill is not always true or false, but rather true or undefined, for example. Make sure it’s always boolean, and the problem will go away.

Is this issue still be considered as bug?
I change to use defaultValue with defaultChecked to make it no warning.

<input type="radio" name="a" defaultValue="1" defaultChecked/>
<input type="radio" name="a" defaultValue="11"/>

@littlee I don't blame you, I blame us, but that's just terrible.

@gaearon
When a component has a value prop and at the same time a defaultValue(or defaultChecked) prop, it would be regard as an uncontrolled component.

I think this should be explicitly declared with the examples in the Form docs , for example write a Note about this situation, or it would missunderstands the React beginers.

@littlee, in short you mean Instead of checked={this.state.isMustFill} do checked={!!this.state.isMustFill}

@kopahead

Instead of checked={this.state.isMustFill} do checked={!!this.state.isMustFill}

I don't think it's related to what I said before

i had same warning. casting value from null to false solves the problem:
<input type="checkbox" checked={!!this.state.someValue} onChange={.....} >

so now i should put !! everywhere in checkboxes

try: checked={(this.state.someValue)} instead which casts to boolean. falsey null/undefined/0/'' = false everything else true. this works just like a ternary operator:

checked={(this.state.item5) ? true : false}

Yet another way is:

checked={this.state.someValue === true}

In my case, the checked value was passed as undefined when the component is loaded. I have passed false when it is undefined.

I used the following code, the warning disappeared.

function CheckboxButton({checked, onChange, textComponent} = {}) {
    return (
        <label className='checkbox-button'>
            <input
                type='checkbox'
                checked={checked || false}
                onChange={onChange}/>
            {textComponent}
        </label>
    );
}
Was this page helpful?
0 / 5 - 0 ratings

Related issues

trusktr picture trusktr  ·  3Comments

varghesep picture varghesep  ·  3Comments

zpao picture zpao  ·  3Comments

framerate picture framerate  ·  3Comments

jvorcak picture jvorcak  ·  3Comments