-
Notifications
You must be signed in to change notification settings - Fork 47.8k
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
RFC: Plan for Attributes in React 16 #10399
Comments
One thing we haven’t quite worked out yet: should we pass numbers and booleans through.
Downsides:
|
We have (mostly) settled on this tradeoff:
@sebmarkbage wants to look into a few more corner cases but this is likely the last tweak. |
Can you clarify what you mean by not passing booleans through? What are we warning about? |
would not render it to the DOM and would display a warning about it not being a string or number. The concern is that browser APIs are not consistent about what This doesn’t affect known boolean attributes. We will still keep them in the whitelist. So you can keep passing |
That's what I was confused about, thanks. |
Thoughts on special casing custom elements? If there is a property on the element with the same name, then use the property instead of the attribute? |
I’d love to see that! But let’s discuss separately? Since there is already a lot to take in here, and @nhunzaker is probably close to exhaustion from working on this 😛 |
Although it would be nice to get changes to custom elements in 16 now that we have a chance. |
For custom elements, let’s follow what Preact does? If a property is defined, use a property, otherwise use attribute. But for objects/arrays always use properties. |
Even for objects/arrays, we should only use properties if one exist. But we should warn and not set if none exist. The problem with detecting properties is that |
What about objects with custom |
Are you talking about custom elements or regular elements (focus of this issue)? Let’s keep this issue focused on regular elements. One doesn’t block the other, and I don’t want bikeshedding over custom elements to black landing this. :-) |
Both. :) |
I don’t think we should ever let For custom elements I’d expect we pass them (as objects) if property exists, and don’t pass otherwise. If you want it to be an attribute IMO you should explicitly call For regular elements we don’t pass them through and always warn. Since you could be accidentally spreading something from a parent component, and |
I think it's OK to use the My understanding is this kind of dilemma comes up a lot with specs because there are all sorts of libraries out there which might use a particular property or function and then become super popular. So spec authors will do an audit of the web and see if their proposed name is already in use. If some other popular library "owns" that name, the spec authors will use a different one. |
@robdodson The difference here is that attribute is the common way. So if the component uses attributes there is no property that shadow the base class. We'll pick it up from the base class and start using it instead of the attribute. This doesn't usually happen with other specs because the common case is that things shadow. E.g. If you do To preserve this invariant we must only use properties and never fallback to attributes. |
What do you mean by the common way? Not sure I follow.
That's true. In general I encourage folks to always create a corresponding property. I'm writing up some best practices docs that cover this.
I think I see what you're saying. The Custom Element might only work off of a In the wild I haven't seen that many Custom Elements that only use attributes. FWIW, if a developer uses Polymer to create their element then it'll create corresponding properties for any attributes.
I think that's probably fine. Personally I prefer properties for everything :) If the Custom Element author is really concerned about this there is a pattern they can use to capture properties set on unupgraded instances. |
@gaearon Nice write-up! Though it feels like a middle-of-the-road approach where we inherit both the naming from convention of DOM properties AND DOM attributes, with it not being obvious to the user when they're supposed to use one or the other. Like you say (if I understood you correctly), this still means the whitelist will live on in some limited way. I think there's already a lot of confusion regarding e.g. Everything needs to be considered (incl legacy) and I can't say I know a better way forward than what you've put forth (and I've gone back and forth in the past trying to wrap my head around it 😄). But ReactDOM has two things to consider; DOM attributes and DOM properties. ReactDOM fundamentally ignores DOM properties and only supports DOM attributes today (naming convention aside), i.e. we only support things that can be rendered to HTML during SSR. However, the proposed naming convention above uses DOM property naming convention but falls back to DOM attribute naming convention for unknown attributes (non-whitelist). Essentially ReactDOM takes two different concepts (attributes and properties) and mixes them into the same namespace. Ignoring legacy this seems very confusing (and jQuery committed the "same mistake" in its early life). It seems like this just becomes even more confusing for e.g. SVG where both camelCase and hyphenated property names co-exist. Custom components adds to this. All while at its core it's a very simple problem to solve if React would not transplant the DOM property naming convention to DOM attributes and then mix the two. So TBH I'm not entirely sure where I'm going with this, but it would be interesting to know what your high-level long-term goal is. Should ReactDOM just be a "dumb" renderer? Should it be more? How much more? (controlled inputs being implemented in ReactDOM vs userspace means we're not simply a dumb renderer, but should ReactDOM extend the same courtesy to video too?). Should we be able to largely copy-paste SVG? HTML? Don't get be wrong, all things considered, what you're proposing causes the least friction and is probably the right way forward given the legacy, but it seems like it's replacing a whitelist with special behavior. Which doesn't seem all that much better other than from the perspective of bundle size. So is there a long-term goal towards a more stable/consistent behavior or is the idea to keep the current special-case for legacy reasons? Or do you perhaps even see this as the right long-term approach? PS. As for |
We were already inconsistent anyway, both with custom elements, and with If we take that into consideration, it seems like the distinction between properties and attributes is not the one that is useful to explain the public API. Whether ReactDOM uses an attribute or a property for a particular “prop” is its implementation detail. Conceptually, the mental model I propose is that:
The last point is important. We’re not calling them We’re only offering That’s my mental model. I agree it’s not ideal but it seems like best compromise we could find.
For now, it’s to solve the above two problems (too large whitelist and no custom attributes) with minimal friction. It’s not a high-level goal, but then we don’t really have ones for ReactDOM at the moment. It works well for most cases, and we’re mostly working on the core these days. I’d argue this change brings ReactDOM closer to how the community expects it to work (and how some other libraries have done it). Will we write applications in five years with ReactDOM? I don’t know. Maybe a higher, more intentionally designed layer like |
The only downside is that it requires special-logic in React as opposed to just plain
I feel that what you're saying is a nice feature for beginners, but far less so for experienced people. When you're more experienced you want to know what to expect in detail up-front, currently it's basically trial-and-error.
That was my first though, my only concern is the compatibility between custom renderers (and obviously the overhead of bundling different ones). Even as-is I suspect it wouldn't really work considering how event handling is implemented. And it also hurts the community if it fragments (everyone using their own slightly different version of the hypothetical "ReactDOMLite").
Yeah I've gotten that feeling, and I fully understand it! 😄 Anyway, I don't mean to hold up the PR, it's an interesting discussion. Thanks for your answer! |
I think the discussion about whether we move closer to mirror attributes or not is orthogonal (haha, I used that word) to this proposal. It is already the case that we sometimes use the one thing, and in other cases the other. We can keep changing our stance on this in the future (and likely will) but it seems like this shouldn’t block the ability to set arbitrary attributes (for which properties don’t exist in the first place). Similarly, I agree with your point about experienced users, but it doesn’t seem to me that this proposal either makes it worse or better. |
**what is the change?:** Adds tests for the following behavior - - Numbers and booleans should be converted to strings, and not warn - NaN, Symbols, functions, and objects should be converted to strings, and *should* warn Going to add tests for the not-warning behavior in a follow-up. These tests are not entirely passing - we either need to change what we expect or change the behavior. **why make this change?:** Gets everyone on the same page about expected behavior, and codifies it in a maintainable way **test plan:** `yarn test src/renderers/dom/shared/__tests__/ReactDOMAttribute-test.js` **issue:** facebook#10399
**what is the change?:** We are testing the behavior of unknown attributes, which has changed since React 15. We want to *not* warn for the following cases - - null - undefined - missing - strings - numbers - booleans **why make this change?:** We want to verify that warnings don't get fired at the wrong time. **test plan:** `yarn test src/renderers/dom/shared/__tests__/ReactDOMAttribute-test.js` **issue:** facebook#10399
**what is the change?:** Adds tests for the following behavior - - Numbers and booleans should be converted to strings, and not warn - NaN, Symbols, functions, and objects should be converted to strings, and *should* warn Going to add tests for the not-warning behavior in a follow-up. These tests are not entirely passing - we either need to change what we expect or change the behavior. **why make this change?:** Gets everyone on the same page about expected behavior, and codifies it in a maintainable way **test plan:** `yarn test src/renderers/dom/shared/__tests__/ReactDOMAttribute-test.js` **issue:** facebook#10399
**what is the change?:** We are testing the behavior of unknown attributes, which has changed since React 15. We want to *not* warn for the following cases - - null - undefined - missing - strings - numbers - booleans **why make this change?:** We want to verify that warnings don't get fired at the wrong time. **test plan:** `yarn test src/renderers/dom/shared/__tests__/ReactDOMAttribute-test.js` **issue:** facebook#10399
**what is the change?:** - booleans don't get stringified - some warnings have changed since we originally wrote this **why make this change?:** The attribute behavior is finalized and now we can test it :D I also found it handy to have a row with a truly unknown attribute, so added "imaginaryFriend". **test plan:** `yarn test src/renderers/dom/shared/__tests__/ReactDOMAttribute-test.js` and comparing the tests to the attribute table **issue:** facebook#10399
* WIP * WIP Add the rest of the tests for what we expect re: unknown attributes **what is the change?:** Adds tests for the following behavior - - Numbers and booleans should be converted to strings, and not warn - NaN, Symbols, functions, and objects should be converted to strings, and *should* warn Going to add tests for the not-warning behavior in a follow-up. These tests are not entirely passing - we either need to change what we expect or change the behavior. **why make this change?:** Gets everyone on the same page about expected behavior, and codifies it in a maintainable way **test plan:** `yarn test src/renderers/dom/shared/__tests__/ReactDOMAttribute-test.js` **issue:** #10399 * WIP Add check that we *don't* warn when handling some unknown attributes **what is the change?:** We are testing the behavior of unknown attributes, which has changed since React 15. We want to *not* warn for the following cases - - null - undefined - missing - strings - numbers - booleans **why make this change?:** We want to verify that warnings don't get fired at the wrong time. **test plan:** `yarn test src/renderers/dom/shared/__tests__/ReactDOMAttribute-test.js` **issue:** #10399 * ran prettier * Symbols and functions passed to unknown attributes should remove and warn * Abstract tests a bit to make them easier to read * Remove Markdown from test names I don't think we use this convention anywhere. * Add an assertion for NaN warning message * Update ReactDOMAttribute test based on attribute fixture **what is the change?:** - booleans don't get stringified - some warnings have changed since we originally wrote this **why make this change?:** The attribute behavior is finalized and now we can test it :D I also found it handy to have a row with a truly unknown attribute, so added "imaginaryFriend". **test plan:** `yarn test src/renderers/dom/shared/__tests__/ReactDOMAttribute-test.js` and comparing the tests to the attribute table **issue:** #10399 * remove imaginaryFriend to resolve conflict * ran prettier
**what is the change?:** When we render unknown props, they get converted to lower-case. This may be unexpected for people, or break what they are expecting to happen. Let's warn in this case and ask them to explicitly pass the lower-cased attribute name. **why make this change?:** To avoid corner case buggy results for users. **test plan:** NOTE: ~~~It does NOT pass right now. This is a known issue. Should we change and just expect a warning, but allow the attribute value to be set? `yarn run test src/renderers/dom/shared/__tests__/ReactDOMAttribute-test.js` **issue:** facebook#10399
* WIP * WIP Add the rest of the tests for what we expect re: unknown attributes **what is the change?:** Adds tests for the following behavior - - Numbers and booleans should be converted to strings, and not warn - NaN, Symbols, functions, and objects should be converted to strings, and *should* warn Going to add tests for the not-warning behavior in a follow-up. These tests are not entirely passing - we either need to change what we expect or change the behavior. **why make this change?:** Gets everyone on the same page about expected behavior, and codifies it in a maintainable way **test plan:** `yarn test src/renderers/dom/shared/__tests__/ReactDOMAttribute-test.js` **issue:** facebook#10399 * WIP Add check that we *don't* warn when handling some unknown attributes **what is the change?:** We are testing the behavior of unknown attributes, which has changed since React 15. We want to *not* warn for the following cases - - null - undefined - missing - strings - numbers - booleans **why make this change?:** We want to verify that warnings don't get fired at the wrong time. **test plan:** `yarn test src/renderers/dom/shared/__tests__/ReactDOMAttribute-test.js` **issue:** facebook#10399 * ran prettier * Symbols and functions passed to unknown attributes should remove and warn * Abstract tests a bit to make them easier to read * Remove Markdown from test names I don't think we use this convention anywhere. * Add an assertion for NaN warning message * Update ReactDOMAttribute test based on attribute fixture **what is the change?:** - booleans don't get stringified - some warnings have changed since we originally wrote this **why make this change?:** The attribute behavior is finalized and now we can test it :D I also found it handy to have a row with a truly unknown attribute, so added "imaginaryFriend". **test plan:** `yarn test src/renderers/dom/shared/__tests__/ReactDOMAttribute-test.js` and comparing the tests to the attribute table **issue:** facebook#10399 * remove imaginaryFriend to resolve conflict * ran prettier
Note: the final plan has changed. Refer to https://facebook.github.io/react/blog/2017/09/08/dom-attributes-in-react-16.html for details on what ends up in React 16.
This is a more formal conclusion of the discussion in #7311.
It is mostly (not yet fully) implemented by #10385.
This is meant to address #140.
I wrote this doc but it’s mostly based on discussion with @nhunzaker. I decided to write it in an attempt to formalize the behavior we want, so that if there are bugs, we can refer back to this.
Current Behavior
React only lets you use “approved” camelCase properties that look organic in JavaScript:
There are two downsides to this.
Problem: Custom Attributes
You can’t pass custom, non-standard, library-specific, or not-yet-standardized attributes:
This is a very popular feature request.
Problem: Maintaining a Whitelist
We currently have to maintain a whitelist of all allowed attributes, and use it even in the production build.
By being more permissive, we can drop ReactDOM size by 7% post-min/gzip without any changes to app code.
Guiding Principles
If we change the current behavior, there’s a few existing principles we want to preserve:
class
andclassName
would be ambiguous and confusing to component authors.I think there is a compromise that lets us solve the problems above without deviating from these principles.
Proposed Behavior: Overview
We drop a large part of the whitelist, but we make the behavior less strict.
These used to be ignored due to wrong casing, but now will be passed through:
Instead of being omitted, they will only emit a warning now.
However, we still don’t pass through attributes that differ in more than casing from React version:
This lets us drop 7% of ReactDOM bundle size and keep most of the whitelist for development only.
Proposed Behavior: In Depth
Let’s say
reactAttr
is the attribute name you use in React, anddomAttr
is its name in HTML/SVG specs.Our whitelist is a map from
reactAttr
todomAttr
.In React 15, it might look like this:
reactAttr
domAttr
className
class
srcSet
srcset
acceptCharset
accept-charset
arabicForm
arabic-form
strokeDashArray
stroke-dasharray
calcMode
calcMode
Proposed Changes to the Whitelist
We remove any attributes where
lowercase(reactAttr) === lowercase(domAttr)
and don’t have special behavior. In other words, we delete any attributes that “just work” in regular HTML.reactAttr
domAttr
className
class
srcSet
srcset
acceptCharset
accept-charset
arabicForm
arabic-form
strokeDashArray
stroke-dasharray
calcMode
calcMode
(This is where we get 7% size savings.)
We still keep the full attribute whitelist in DEV mode for warnings.
Proposed Changes to the Algorithm
Let’s say
givenAttr
is the attribute in user’s JSX.We follow these steps now:
Step 1: Check if there exists a
reactAttr
in the whitelist equal to thegivenAttr
.If it there is a match, use the corresponding
domAttr
name from the whitelist and exit.For example:
This matches behavior in 15.
If there is no match, continue.
Step 2: Check if there exists a
domAttr
in the whitelist thatlowercase(domAttr) === lowercase(givenAttr)
We’re trying to determine if the user was using a DOM version of attribute that is sufficiently different from the one suggested by React (that is, by more than casing).
In this case, don’t render anything, warn and exit.
So far this matches behavior in 15.
Note that this does not catch the cases that were sufficiently similar that we excluded them from the whitelist. For example:
This is because we don’t keep them in the whitelist anymore.
If we hit such case, continue below.
Step 3: If the value type is valid, write
givenAttr
to the DOM, with a warning if it deviates from React canonical API.This is where we deviate from 15.
If we reached this stage, we render it to the DOM anyway, which may or may not be successful:
We only render strings and numbers.
If the value is of a different type, we skip it and warn.
For numbers, we also warn (but still render it) if the value coerced to string is
'NaN'
.Success now depends on whether DOM accepts such an attribute.
However, we will still warn if there is a
reactAttr
thatlowercase(reactAttr) === lowercase(givenAttr)
.In other words, we warn if there is a canonical React API that differs in casing, such as for all above cases.
This step also captures the new requirement. Any completely unknown attributes will happily pass through:
Other Considerations
ariaSomething
). This is a minor deviation from “pass everything through” approach but seems sensible.style
) has not changed.The text was updated successfully, but these errors were encountered: