Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Can't update 'checked' attribute #6321

Closed
jdelafon opened this issue Mar 23, 2016 · 29 comments
Closed

Can't update 'checked' attribute #6321

jdelafon opened this issue Mar 23, 2016 · 29 comments

Comments

@jdelafon
Copy link

Consider this input:

<input
    type='radio'
    value='2'
    checked={this.state.value === '1'}
    onChange={this.onChange}
/>

onChange: function (e) {
    this.setState({value: e.target.value});
}

If the first time the component is rendered, this.state.value === '1', then this input will remain checked forever in the DOM, even if it appears unchecked visually. For instance this can happen with

getInitialState = function() {
    return {value: '1'};
}

I am using React 0.14.7.

@zpao
Copy link
Member

zpao commented Mar 23, 2016

I can't reproduce this (or maybe am misunderstanding what you're saying). https://jsfiddle.net/rkc64ybp/ is showing that the node is unchecked in the DOM. You might run into issues if you use e.preventDefault() but I don't see that in your code here.

@aweary
Copy link
Contributor

aweary commented Mar 23, 2016

@zpao I believe he means that, even after unchecking it, the <input> element still has the checked attribute if you inspect it in devtools. That seems to be the case on my end, at least.

Chrome 49.0.2623.87 (64-bit) FWIW.

@jdelafon
Copy link
Author

Yes, that is what I meant, sorry if it wasn't clear. I tried Firefox and Chrome.
It is not really a problem for user experience, but it is for functional testing (although a workaround is to set className={value === 1 ? 'checked' : ''} and use the class as a selector).

A screenshot of the fiddle with the debugger showing checked="" while the button is unchecked:
http://postimg.org/image/9jjk26tjx/

The strange thing is that indeed e.target.checked is false. So can it be a devtools bug in both Firefox and Chrome ?

The contrary effect is also true: I have initially unchecked inputs that never get the checked attribute when clicked, although they get selected visually, and e.target.checked returns true.

@zpao
Copy link
Member

zpao commented Mar 23, 2016

Ah, I see. The attribute is set but the property isn't. That will be solved in v15 where we will remove the attribute instead of just setting the property to false. See https://jsfiddle.net/rkc64ybp/1/ (same code, just with hasAttribute calls and upgraded to 15, I saw the issue with 0.14)

@aweary
Copy link
Contributor

aweary commented Mar 23, 2016

@zpao should React add the checked attribute to the <input> element when it's checked in v0.15? With that JSFiddle it just looks like it doesn't add the checked attribute at all. Or does it just handled checked state internally and not set the checked attribute at all?

@zpao
Copy link
Member

zpao commented Mar 23, 2016

That's a good question… @syranide - was there any discussion about that?

@syranide
Copy link
Contributor

@zpao There was/is a separate discussion about inputs and value somewhere else (cc @spicyj). I'm not sure where it ended up but it was about going away from the native behavior seen here to actively update the attribute in the DOM (EDIT: perhaps not intentionally, but that was the result). However, it should be noted that the behavior seen here is expected behavior, you get this behavior with native checkboxes as well; the checked property is updated, the attribute is not as it reflects the initial state.

@jdelafon
Copy link
Author

Ah, I did not know that it was the native behavior... Then it is not a React issue at all, and I don't think React should try to have a different behavior. That sucks though, it means for instance that one should never use the input:checked CSS selector anyway to style the currently selected element, and a className='checked' is the only way Selenium can find it.

@syranide
Copy link
Contributor

@muraveill I would assume input:checked works and that's specifically why it was added, but not input[checked] (tests for attribute).

@sophiebits
Copy link
Collaborator

Once again, I'll repeat that it does not make sense to talk about the native behavior here. That is, HTML does not have a concept of going from

<input type="checkbox">

to

<input type="checkbox" checked>

We created this concept. In HTML, you can do a property assignment (.value =), attribute assignment (.setAttribute), or you can blow away the entire thing and replace the whole node with .innerHTML. You can also have the user click it.

We can decide for any of these to be the behavior that React matches. None of them is the objectively correct "native behavior" so it's misleading to talk as if that were the case.

It seems like setting the attribute is least confusing for people in most cases (so that both [checked] and :checked would work), but it's still not clear whether that's best, especially with forms.

@syranide
Copy link
Contributor

Once again, I'll repeat that it does not make sense to talk about the native behavior here. That is, HTML does not have a concept of going from

We can decide for any of these to be the behavior that React matches. None of them is the objectively correct "native behavior" so it's misleading to talk as if that were the case.

I still don't buy it, the specification explicitly states that the attribute reflects the initial value and the property reflects the current value. So if you check it without any additional scripting applied, this is the behavior you get, you're also meant to generally update it via .checked (then it's consistent with user interaction). What's being asked here is not something you can do with plain native checkboxes, so if React does something that enables it, how is that not deviating? But yes, React doesn't really fit the HTML model perfectly so it's fine, but I think there's quite clearly a native behavior.

Anyway, I don't want to derail this.

@sophiebits
Copy link
Collaborator

Why are you "meant to" update it with the property? .setAttribute() is an equal here.

@syranide
Copy link
Contributor

If you want to simulate user input/filter/normalize/etc then you use the property so you don't change the initial value. If you want to replace the value (i.e. changing data source) then you use the attribute (similar to setting the key in React), you're "resetting" the checkbox in a sense. Again I agree this differentiation doesn't really work well in React (and there's no good reason to), but there is a difference.

I don't really mind, it doesn't really have any major implications, but it is clearly different from plain IMHO.

@jimfb
Copy link
Contributor

jimfb commented Mar 25, 2016

Conceptually, upon every change, we throw away the entire page and rerender from scratch. Ideally, the output of an initial render should be identical to the output of an initial render followed by an update render, assuming the final state is the same.

@syranide
Copy link
Contributor

@jimfb Yes, but it's a partial truth, inputs also have focus, selection, undo history, etc and in the context of this discussion, they also have an initial value that is reflected in the attribute. We currently control the lifetime of internal and external state via key in React and it makes sense, even for seemingly controlled components. If you don't agree with HTMLDOM reflecting the initial value in the attribute that is fine, it's a weird HTML quirk, but if interacting with a plain checkbox (attribute not updated) and a React checkbox (attribute updated) produces different results then React is doing something differently. Again, IMHO that's fine, but it is clearly deviating from normal HTMLDOM behavior.

@rovolution
Copy link

rovolution commented May 3, 2016

had a breaking change in one of my unit test suites regarding this issue when upgrading from 0.14 -> 0.15...I built a custom checkbox component that wraps an <input type="checkbox" /> and setting the checked prop on it (which in turn set the HTML attribute) . Personally, I prefer when it sets the "checked" attribute on the <input> element

@krzysu
Copy link

krzysu commented May 4, 2016

I agree with @rovolution. I see similar case in my code, I use radio inputs like this (a few of them):

<input
    type="radio"
    name={attributeName}
    checked={config.selected}
    (...)
/>

on every change value of config.selected is changed. In the browser it works correctly, but in dev mode I'm getting this in the console:

Warning: React attempted to reuse markup in a container but the checksum was invalid. This generally means that you are using server rendering and the markup generated on the server was not what the client was expecting. React injected new markup to compensate which works but you have lost many of the benefits of server rendering. Instead, figure out why the markup being generated is different on the client or server:
 (client) <input type="radio" name="color"
 (server) <input type="radio" checked="" name="color"

what means the way you render on the server is different than on the client.
after @syranide: "the checked property is updated, the attribute is not as it reflects the initial state" <- from what I see the attribute is removed on the client

@syranide
Copy link
Contributor

syranide commented May 4, 2016

@krzysu <input type="radio" name="color" means that it is unchecked and <input type="radio" checked="" name="color" means that it is checked. You're rendering different markup on server vs client hence the error/warning.

@krzysu
Copy link

krzysu commented May 4, 2016

yes, I know that, but that happens only after upgrading to react v15. so the way new react is rendering inputs is different between client and server, and that's why I'm writing here.
Client removes checked attribute (because of new logic) causing checksum to be invalid, causing warning.
Component props are exactly the same on server and client. Only react way of rendering is different.

@syranide
Copy link
Contributor

syranide commented May 4, 2016

@krzysu I'm unable to reproduce this myself. Rendering a checked radio input on the server and then resuming on the client works just fine for me, no mismatch error.

@krzysu
Copy link

krzysu commented May 4, 2016

ok, I will try to prepare an example which reproduces it

@rovolution
Copy link

rovolution commented May 4, 2016

Here are some GIFs with my custom checkbox:

React 0.14.0 (setting checked attribute):
with-checked

React 15.0.0 (checked attribute not set):
wihout-checked

In the code (jsx), I am just setting the checked attribute to true on an element of type="checkbox"

@krzysu
Copy link

krzysu commented May 6, 2016

ok, so my issue is different that the one from @rovolution

@syranide as promised, I extracted my code into a reproducible example, which you can find here https://github.com/krzysztofurbas-home24/react15-input-checked-issue.
I've found so far that the problem is caused by some of the dependencies and very specific version of it. That's why I included npm-shrinkwrap.json. If you remove npm-shrinkwrap, and reinstall node_modules, then the reported issue is gone. So I'm not sure if it's worth debugging 😉

@syranide
Copy link
Contributor

syranide commented May 6, 2016

@krzysu 👍 Are you using npm3? npm2 has a problem with its hierarchical dependencies such that some plugins may depend on older versions of React and have those be installed side-by-side. They are not compatible and will generate slightly different markup which can cause the problem seen above, which I also find likely considering that the subsequent attributes are not even the same (order) in client/server output warning. This generally shows itself differently in my experience, so who knows, but some external versioning conflict obviously.

@CoderK
Copy link
Contributor

CoderK commented Apr 10, 2017

@rovolution
I have the same issue in [email protected]

from component.jsx

    render() {
        const { name, label, title } = this.props;
        const buttonClass = classnames(
            "se3-toggle-button",
            `toolbar-button-${name}`,
            "toggle-checkbox"
        );
        const checkboxId = `se3-toolbar-${name}-button`;
        const isChecked = this.state.checked;
        const currentTitle = title[isChecked];

        return (
            <div className={ buttonClass }
                title={ currentTitle }
                data-type={ ButtonTypes.TOGGLE }
                data-name={ name }>
                <input type="checkbox"
                    id={ checkboxId }
                    className="se3-toolbar-checkbox"
                    checked={ isChecked }
                    onChange={ this.handleChange }
                    data-role="item" />
                <label htmlFor={ checkboxId }
                    className="se3-toolbar-label">
                    <span className="se3-toolbar-icon"></span>
                    <span className="se3-toolbar-label-text">{ label }</span>
                </label>
            </div>
        );
    }

to html

<input type="checkbox" id="se3-toolbar-bold-button" class="se3-toolbar-checkbox" data-role="item" value="on">

When I click checkbox, It has been changed checked property internally. But the checked attribute
of checkbox element's was not displayed in chrome inspector.

image

image

@mstijak
Copy link

mstijak commented Apr 25, 2017

I'm seeing the same problem in all browsers. Unfortunately, it's difficult to extract a smaller example reproducing the issue.
image

My workaround is to assign a different key based on the value, but I really hate it.

@domenikasd
Copy link

domenikasd commented Apr 25, 2017 via email

@domenikasd
Copy link

domenikasd commented Apr 25, 2017 via email

@gaearon
Copy link
Collaborator

gaearon commented Oct 3, 2017

I’ll close since there’s a few different things discussed here, and it’s hard for me to say what’s the status on either of them.

Please file a new issue with a reproducing example if you still experience problems related to this.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests