Open edoardocavazza opened 8 years ago
I wanted to +1 this issue, I think it would really help out with Custom Element interop. I don't know if React needs to watch observedAttributes
, instead it could do something like Preact and check for the property on the node. Or if that's too much work just default to setting properties all the time.
Any custom element built with Polymer (and I believe SkateJS) will have property setters auto-generated so you can rely on them being there for those elements. And we're encouraging developers to rely on properties as their source of truth when building custom elements.
cc @blasten @sebmarkbage @treshugart
I'm leaning towards a properties-if-available model too.
if (propName in obj) {
obj[propName] = value;
} else {
obj.setAttribute(propName, value);
}
Properties-if-available is what we've done in Skate with our Incremental DOM wrapper FWIW. However, I've recently been messing around with an alternative approach, similar to the proposed events
in #7249.
That repo is only a playground for me at the moment, but I really like how you have full control over what is set as properties, attributes and events by having special attributes
and events
props. I definitely think that's worth considering as there's no error prone internal checks that need to be done. For example, the proposed approach here doesn't work with the custom element polyfill because it's possible that it wouldn't have been upgraded yet (thus have upgraded props) by the time it does the propName in obj
check.
@robdodson
Any custom element built with Polymer (and I believe SkateJS) will have property setters auto-generated so you can rely on them being there for those elements.
Yup, we have first class props.
There's a possibility of a raise condition between upgrading a custom element and running DOM reconciliation. For example:
The DOM is initially server-side rendered and the app runs on a browser that doesn't have native CE. React reconciles the DOM, but because the polyfill upgrades CEs in the next micro task, React might set an attribute or a property depending on the order of those calls. If set as an attribute and the value is an object, then the CE will end up with prop="[Object object]"
or prop="1,2,3,4"
.
Similar issue, but this time the CE upgrades asynchronously. If React were to always set the values as properties, then a CE constructor could check if a property is defined in the instance. e.g.: If it's an instance prop, remove it and call the setter in the prototype instead. Not great, but better than [Object object]
.
Any thoughts?
@blasten is correct. If an element is not-upgraded when React sets props, then attributes would be set, and once it's upgraded the element wouldn't have access to the props like it would with properties.
+1 for what @blasten said. After having messed around with the method I linked to in my last comment, I can say it's a very robust approach. I'm not sure there is a fool proof method other than giving the consumer full control. A positive side effect is that there's less logic necessary to decide what to do.
As a proof of concept, for this (and #7901) I've created a gist. It is a function that wraps any hyperscript style vdom function and enables full control over props, attributes and events (including custom events) for any vdom implementation that supports ref
. It even allows one to pass a custom element constructor as the component name. Details of the implementation are in the readme of the gist.
Thanks for putting that proposal together @treshugart. I think it would be great if all frameworks supported this contract:
render() {
return
<div ref="scroller" style={{ overflowY: 'auto' }}>
<custom-element props={{
onSomething: () => {},
ariaLabel: 'Tap',
scroller: () => this.ref.scroller
}}></custom-element>
</div>;
}
class CustomElement extends HTMLElement {
set props(props) {
let prevProps = this._props;
// check prevProps.onSomething !== props.onSomething
// check prevProps.ariaLabel !== props.ariaLabel
this._props = props;
debounce(this._render);
}
get props() {
return Object.assign({}, this._props);
}
}
The CE props
setter can add event listeners or set attributes when necessary and call it's equivalent render
function. The CE can also support setting those props as attributes as long as it knows how to deserialize them. (Mostly for declarative HTML documents) For example:
<div id="scrollingElement" style="overflow-y: auto;">
<custom-element on-something="foo()" aria-label="tap" scroller="scrollingElement">
</custom-element>
</div>
What I like about this setup is:
if (this.hasOwnProperty('props')) {
let userProps = self.props;
delete this.props;
this.props = userProps;
}
I'm curious about your guys thoughts. cc @addyosmani @robdodson @developit @justinfagnani
A possible proposal is for CE authors to always use the properties pattern that @blasten has shown, although I don't know if we want to encourage everyone to put everything into a single props
object. Instead we might want to do something like:
upgradeProperty(prop) {
if (this.hasOwnProperty(prop)) {
let value = this[prop];
delete this[prop];
this[prop] = value;
}
}
Then the Custom Element author could use this approach with any properties their element exposes.
Here's a jsbin that illustrates this technique. h/t to @sjmiles
I believe if CE authors use this technique then React/Preact should be able to always set properties and not worry about attributes. And this will avoid situations where someone ends up with
<x-foo zerp="[object Object]">
.
For events I still think it's preferable to use addEventListener
as discussed in https://github.com/facebook/react/issues/7901. Maybe by adding some additional syntax or a heuristic to make this a bit nicer.
There are some cases, e. g. text input's value
, where React both assigns a property AND sets the corresponding attribute. Maybe this option could be helpful here as well.
I think the issue with setting both is that you can't assign an object or array to an attribute. You have to stringify it first. Setting both at the same time may create extra work on the element's behalf because depending on the order in which you set things, it'll likely need to process the change twice.
I'm working with a teammate to produce some custom element examples that use the pattern I previously mentioned. I'll follow up on this thread when we publish what we've come up with to see what others think.
@robdodson one property vs many properties is a personal choice, but one property (e.g. props
) has several advantages over the later:
namespace. I remember, polymer users tried to have properties such name
or id
, but they realized that those properties collide with the ones inherited from HTMLElement
causing inconsistencies.
The upgradeProperty
check becomes O(n)
if you have n
properties, also the boilerplate code grows as the number of properties increases. Unless, a lib defines those properties dynamically (via Object.defineProperty
) which can also slow down FPT/TTI.
The getter props
returns the exact current state of the element; making it easier to transition to a new state.
Could have the potential to map to a prop-
attribute, in the same way dataset
maps to data-
.
yeah I agree there are advantages to just using props
. And maybe everyone eventually goes in that direction. I didn't want to assert too specific of a pattern since all of this is still pretty unexplored territory.
First I wanted to see if we could encourage React and other libraries to always set properties, while at the same time encouraging Custom Element authors to adhere to the pattern of grabbing properties off of the instance if they've already been set. If both of those worlds start to gel, then the next step would be to explore ways to optimize it, possibly using the props
pattern.
Objectively, discussing props as a single entry point for all properties is orthogonal to this issue, and highly contentious as a perf opt. Probably best to leave this to CE libs to implement if they see fit. Its prescriptive nature places it in a higher level of abstraction than the lower level browser APIs, and doesn't fall in line with how all elements expose their API as several top level props.
I still feel the consumer (React component authors) should have full control over what they want to do. If I want to set an attribute, or add an event listener, I should be able to do so without having to workaround React only setting props or an inadequate custom element implementation.
Everything always being properties is an ideal and we live in an imperfect world.
I still feel the consumer (React component authors) should have full control over what they want to do. If I want to set an attribute, or add an event listener, I should be able to do so without having to workaround React only setting props or an inadequate custom element implementation.
Yeah I'd be cool with this as well :)
I agree that the scope of this thread isn't discussing such patterns. I just brought it up because I found it useful, but my intention wasn't to ask the react team to implement the CE integration that way. After all, they should support what is on the spec. I was just showing a potential pattern. I could have called my property "user" instead of "props"
I was just showing a potential pattern. I could have called my property "user" instead of "props".
👍 that's an interesting pattern in itself, and definitely something that CE authors should consider :)
Is there any investigation on this topic? It`s really annoying that we have still problems with custom components :/
We are not working on this but it makes sense to change the behavior to a more useful one in React 17.
However it will require somebody to do the actual research into different options, weigh their pros and cons, and come up with a comprehensive proposal for the changes they want to see.
Would you like to start working on this?
I'm happy to help if I can. I'm not much of a React expert, but I think I have a pretty good idea of how things should work on the custom element side.
I was wondering if there's been any thought toward supporting the same model that Preact uses? If it would help I could put together a doc that outlines their approach?
I'd be happy to help implement the changes in React once a plan has been formulated.
Yes, a doc with the proposal would help. The only concern I know about Preact model is that adding a new property to the base class in the platform would become a breaking change for React/Preact users.
@gaearon ok I can put together a doc and ping this thread for review when it's ready
sounds good! @robdodson thx for the doc idea.
I think I've got most of the doc written. I'm reaching out to a couple people for feedback on the draft and should be able to share next week :)
Sweet, thanks!
I'm getting increasingly skeptical of the properties model.
1) It's infeasible to only use properties in the current ecosystem it seems. So the other alternative is to feature test for a property and if it's not the there use the attribute. This basically prevents the DOM from adding any new properties on at least Node/Element/HTMLGenericElement. Possibly even more if sub-classing is enabled. Since if a property is added on a parent class, we'd switch to use that. Maybe we could do hasOwnProperty only but even that would be sketchy and break many use cases. https://twitter.com/sebmarkbage/status/894678380225875968
2) There's no server-rendering story into custom elements. Because we have no way of knowing how a property translates to the equivalent attribute or if they even have the same names. We'd have to render no attributes and then only add the properties on the client.
It seems to me that the two namespaces can't reliably be combined. So the only alternative is to provide two namespaces in React which is not really idiomatic.
Possibly even more if sub-classing is enabled.
According to https://html.spec.whatwg.org/multipage/custom-elements.html#custom-elements-core-concepts, customized built-in elements should not add new attribute names other than data-*
ones. Does this cover the sub-classing concern, or is there some other form of sub-classing that we're concerned about?
This basically prevents the DOM from adding any new properties on at least Node/Element/HTMLGenericElement
This does seem like a potential problem though. Also, at any moment in time, there may be browser differences. For example, contextmenu
is a global attribute currently supported in Firefox (and for which contextMenu
is automatically added as a DOM property), but not in Chrome. It's currently part of the HTML 5.1 specification, but I don't know if it will get removed from there. See https://github.com/w3c/html/issues/589. Seems quite possible for there to be other cases of global property additions into HTML5 in the future, which potentially would introduce breaking changes to custom element implementations as well as to a React strategy of basing an attribute/property decision on the property existing.
It seems to me that the two namespaces can't reliably be combined. So the only alternative is to provide two namespaces in React which is not really idiomatic.
I agree that it wouldn't be idiomatic if it were specified in the JSX via any sort of subdivision of props into sub-objects. (e.g., this.props.attributes, this.props.properties, etc.).
But what about providing an API for application code to inform ReactDOM
about the semantics of the custom element? E.g.,
ReactDOM.defineCustomElementPropertyConfig(elementName, propertyConfig)
where propertyConfig
could be an object similar to HTMLDOMPropertyConfig, but only for the properties defined by the custom element rather than by the platform, and still use HTMLDOMPropertyConfig
itself for the properties defined by the platform.
I posted an RFC to discuss the pros and cons of a few different options over at https://github.com/facebook/react/issues/11347
any advance of this feature? Thanks in advance 👍
I'm keen to see this feature implemented in React. Thanks a lot for your efforts making React great.
This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contribution.
Sorry, I have no other choice:
+1
This issue has been automatically marked as stale. If this issue is still affecting you, please leave any comment (for example, "bump"), and we'll keep it open. We are sorry that we haven't been able to prioritize it yet. If you have any additional information, please include with in your comment!
bump
This issue has been automatically marked as stale. If this issue is still affecting you, please leave any comment (for example, "bump"), and we'll keep it open. We are sorry that we haven't been able to prioritize it yet. If you have any new additional information, please include it with your comment!
Still valid. Is there a way to disable the stale bot for this issue? It just spams & requires others to spam as well...
This issue has been automatically marked as stale. If this issue is still affecting you, please leave any comment (for example, "bump"), and we'll keep it open. We are sorry that we haven't been able to prioritize it yet. If you have any new additional information, please include it with your comment!
I believe this should not be marked as stale.
Bumping before it gets marked as stale again.
Doing my part to foil the stale bot!
IMHO, it's a crime that React is the only major JavaScript framework that does not support Web Components
There's an active discussion in https://github.com/facebook/react/issues/11347#issuecomment-877327383 that you're welcome to contribute to if you care about this topic.
This issue has been automatically marked as stale. If this issue is still affecting you, please leave any comment (for example, "bump"), and we'll keep it open. We are sorry that we haven't been able to prioritize it yet. If you have any new additional information, please include it with your comment!
Hey bot, bump to the bump, to the bump bump bump. Maybe this will be the last bump though? 🤞🏻
Do you want to request a feature or report a bug? Feature What is the current behavior? Custom component's properties are always set as attribute. What is the expected behavior? Maybe React should watch at the static
observedAttributes
property for custom elements (https://w3c.github.io/webcomponents/spec/custom/) and then decide to set an attribute or an instance property. Otherwise, objects and array could be always passed as properties, in order to avoid<custom-element prop="[object Object]"></custom-element>
.