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

Attributes not removed when set to null on updates #1448

Closed
steadicat opened this issue Apr 24, 2014 · 18 comments
Closed

Attributes not removed when set to null on updates #1448

steadicat opened this issue Apr 24, 2014 · 18 comments

Comments

@steadicat
Copy link

The behavior of attributes with a value of null or undefined is inconsistent. On first render they are not present on the element at all. On subsequent renders, the attribute sticks around with an empty value.

For example, <a href={null} /> renders what you expect (<a />), but if the value of href changes to null after the first render, you get <a href />. In the case of href (and likely others) an empty attribute has unintended side effects.

See this fiddle: http://jsfiddle.net/pGG2A/1/

@syranide
Copy link
Contributor

cc @sebmarkbage @benjamn @zpao

href and most "attributes" are handled as properties by React, and not attributes, because it's (supposedly) significantly faster. Properties do not understand null or undefined and thus their empty state becomes "". So by default, a property is "" and does not show in the DOM, but by setting it to "" it will show up in the DOM (yay HTML).

http://jsperf.com/propattr/2

Some benchmarking shows that it's not as clear in practice, what's clear though is that all the slow browsers are enormously faster at dealing with properties, Properties being roughly 3 times faster than attributes with the mixed test roughly in the middle. But we're only really interested in removing attributes, so that leaves us with the current implementation being roughly 50-100% faster than removeAttribute, although IE8 being the slowest does not appear to fare as badly, which is good.

Considering even poor IE8 can churn through 300.000 property assignments and attribute removes per second (or 5000 per frame at 60 FPS, IE9 can do 15000 per frame). Couple that with toggling between values and explicitly null/undefined being a rather uncommon case (on huge amounts of nodes and only on properties), I would be surprised to see any measurable real life impact of switching to removeAttribute.

So it seems to me that, unless my tests are seriously flawed, that switching to removeAttribute could actually be viable. It would keep the DOM clean (and would let us do away with the ugly defaultValue probing ⭐ ), and apart from IE11, it is actually faster in my edge browsers (although IE11 is sadly by far the slowest browser in this test (if you can call it that)).

Incidentally, this will also solve #1431 (in a preferable way even, perhaps).


Percentages are relative to current implementation, 2nd vs 1st and 3rd vs 1st respectively. The op/s per second is total assignments + removes per second for the 1st column (2nd test). So unless we want to go all-out attributes, only the first column is of interest.

VMIE8: 17% / 40% slower (~300k/s)
VMIE9: 39% / 69% slower (~900k/s)
VMIE10: 45% / 70% slower (~950k/s)
IE edge: 51% / 75% slower (~1000k/s)
Chrome edge: +44% / +19% *faster* (~4000k/s)
FF edge: +27% / +1% *faster* (~7800k/s)

@syranide
Copy link
Contributor

Proper implementation #1510

@sebmarkbage
Copy link
Collaborator

What is the consequence of leaving the DOM attribute there? Is it messing with your CSS rules? What is the end problem caused by leaving them in?

@syranide
Copy link
Contributor

@sebmarkbage <progress /> is different from <progress value="" />, but that's probably the only exception (I know of at least). CSS-rules is an obvious one I guess, although extremely rarely used it seems, but there are some legit uses.

As I mention in my PR, FF and Chrome are actually faster with the attribute methods. Although older browsers suffer slightly (still blazingly fast though).

@sebmarkbage
Copy link
Collaborator

I think it might make sense to have several different code paths based on browser and/or the attribute used. Before committing to this as a permanent supported solution, I'd like to clarify that this is a common enough problem.

There is another reason I'd like to treat React props as properties rather than attributes in general. HTML/SVG and Web Components doesn't allow complex types as attributes. To support complex types like style, class lists, custom user data, we prefer to model these as properties rather than attributes.

If a property is inherently broken in the DOM (missing functionality) we can fix those on a case-by-case basis. Ideally the behavior of React props should correspond to DOM properties instead of DOM attributes. But in the end, we'll do whatever makes sense.

@syranide
Copy link
Contributor

@sebmarkbage My proposed solution only uses removeAttribute to clear (non-boolean) properties (it does not use setAttribute), so unless it interacts poorly with the techs your mentioned, it doesn't seem to me like the use of removeAttribute on properties itself would be an issue.

@ghost
Copy link

ghost commented Jun 19, 2014

Just bringing in the reason it's a problem from my closed duplicate - when a link has an empty href it still takes focus. I can kind of sort out the style problems with extra css but it'll still get focus as you tab the focus through the controls on the page.

@jaredatron
Copy link

@sebmarkbage <a /> is different from <a href="" />

@ariabuckles
Copy link
Contributor

I'm also running into the <a href /> thing (demo http://jsfiddle.net/uLk47d8y/1/ ).

In practice, this means that our markdown editor isn't making things appear as non-links when they become invalid.

I can't comment on the implementation or html properties vs attributes (yuck :( ), but this is surprising as a user. I'll probably just leave it and let it be confusing, but if this behaviour wasn't acceptable, working around it would be a bit painful (key the <a> tag so it becomes remounted?), so here's my small vote for fixing this.

@jaredatron
Copy link

I decided to swap out the a element for a span because just toggling the presence of the href attribute isn't possible atm.

@syranide
Copy link
Contributor

I have PR #1510 for this which seems rather safe (perhaps need to add a test for "property-only" properties), nag on @sebmarkbage a bit more and he might consider it. ;)

grahammendick added a commit to grahammendick/navigation that referenced this issue Mar 22, 2015
Setting the href attribute to null created <a href /> meaning it was
still an active link (see
facebook/react#1448). So swapped the anchor
for a span instead
@ezequiel
Copy link

@syranide @sebmarkbage

is different from , but that's probably the only exception (I know of at least). CSS-rules is an obvious one I guess, although extremely rarely used it seems, but there are some legit uses.

Apparently the dir attribute may be part of the exception as well. See http://jsbin.com/pasicazexe/3/edit.

Things to note: The html element's dir attribute has been set rtl, and the input element's dir attribute has been set to "" (no value). The problem is the above html behaves differently in Chrome and Firefox. I suppose this is expected, as the only valid values for dir are ltr, rtl, and auto. I assume the behavior of the value "" (no value) is undefined, or perhaps the browser ignores it (Chrome doesn't seem to in this case).

For now, my component has this in place:

componentDidUpdate: function() {
    if (!this.props.dir) {
        this.getDOMNode().removeAttribute('dir');
    }
}

@syranide
Copy link
Contributor

@ezequiel Ah good to know.

@abrgr
Copy link

abrgr commented Jun 12, 2015

I'm also getting bit by this, specifically for hrefs on anchor tags. Here is a minimal test case of the very unexpected behavior: https://jsfiddle.net/vsa3Ln40/1/

I'm happy to make the changes if the maintainers agree that the current behavior is unexpected enough to warrant a change.

@tgecho
Copy link

tgecho commented Jul 9, 2015

href on anchor tags is an issue for me as well. When the href is set to null or undefined the link remains active, and clicking on it has the effect of removing anything after # in the url. My temporary workaround is to add a key to the jsx output of my custom link component.

<a href={href} key={href ? 'y' : 'n'}>

@quantizor
Copy link
Contributor

I opened a bug against the IE team for this: https://connect.microsoft.com/IE/feedbackdetail/view/1525237/removeattribute-slowness-compared-to-other-browsers

Feel free to upvote it, all.

@syranide
Copy link
Contributor

#1510 has landed in v15 and this is practically solved (except for a special-case #6119).

@robozb
Copy link

robozb commented Jan 10, 2023

Why React removes included attributes from any html tag autonomously? It can cause css selection problem :(

This issue was closed.
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