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 and properties for Custom Components #7249

Open
edoardocavazza opened this issue Jul 12, 2016 · 47 comments
Open

Attributes and properties for Custom Components #7249

edoardocavazza opened this issue Jul 12, 2016 · 47 comments

Comments

@edoardocavazza
Copy link

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>.

@robdodson
Copy link

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

@sebmarkbage
Copy link
Collaborator

sebmarkbage commented Dec 9, 2016

I'm leaning towards a properties-if-available model too.

if (propName in obj) {
  obj[propName] = value;
} else {
  obj.setAttribute(propName, value);
}

@treshugart
Copy link

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.

@treshugart
Copy link

treshugart commented Dec 10, 2016

@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.

@blasten
Copy link

blasten commented Dec 12, 2016

There's a possibility of a raise condition between upgrading a custom element and running DOM reconciliation. For example:

  1. 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".

  2. 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?

@justinfagnani
Copy link

@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.

@treshugart
Copy link

+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.

@treshugart
Copy link

treshugart commented Feb 28, 2017

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.

@blasten
Copy link

blasten commented Mar 5, 2017

Thanks for putting that proposal together @treshugart. I think it would be great if all frameworks supported this contract:

  • Is it a custom element?
    • Set everything as a property, including events and traditional HTML attributes.

React, Preact:

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:

Declarative HTML

<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:

  1. Simple.
  2. A single setter, which drastically reduces the boilerplate for CE authors.
  3. Can supports async upgrades because the CE constructor can do something like this:
 if (this.hasOwnProperty('props')) {
    let userProps = self.props;
    delete this.props;
    this.props = userProps;
  }
  1. No expensive deserialization.
  2. No race conditions. Properties are the source of truth.

I'm curious about your guys thoughts.
cc @addyosmani @robdodson @developit @justinfagnani

@robdodson
Copy link

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 #7901. Maybe by adding some additional syntax or a heuristic to make this a bit nicer.

@Hypnosphi
Copy link
Contributor

Hypnosphi commented Apr 23, 2017

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.

@robdodson
Copy link

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.

@blasten
Copy link

blasten commented Apr 26, 2017

@robdodson one property vs many properties is a personal choice, but one property (e.g. props) has several advantages over the later:

  1. 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.

  2. 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.

  3. The getter props returns the exact current state of the element; making it easier to transition to a new state.

  4. Could have the potential to map to a prop- attribute, in the same way dataset maps to data-.

@robdodson
Copy link

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.

@treshugart
Copy link

treshugart commented Apr 26, 2017 via email

@robdodson
Copy link

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 :)

@blasten
Copy link

blasten commented Apr 27, 2017

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"

@treshugart
Copy link

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 :)

@haugthom
Copy link

Is there any investigation on this topic? It`s really annoying that we have still problems with custom components :/

@gaearon
Copy link
Collaborator

gaearon commented Oct 17, 2017

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?

@robdodson
Copy link

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?

@jdnichollsc
Copy link

any advance of this feature?
Thanks in advance 👍

@jfhector
Copy link

jfhector commented Apr 6, 2019

I'm keen to see this feature implemented in React. Thanks a lot for your efforts making React great.

@stale
Copy link

stale bot commented Jan 10, 2020

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.

@stale stale bot added the Resolution: Stale Automatically closed due to inactivity label Jan 10, 2020
@Hypnosphi
Copy link
Contributor

Sorry, I have no other choice:

+1

@stale stale bot removed the Resolution: Stale Automatically closed due to inactivity label Jan 10, 2020
@stale
Copy link

stale bot commented Apr 9, 2020

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!

@stale stale bot added the Resolution: Stale Automatically closed due to inactivity label Apr 9, 2020
@effulgentsia
Copy link

bump

@stale stale bot removed the Resolution: Stale Automatically closed due to inactivity label Apr 9, 2020
@stale
Copy link

stale bot commented Jul 11, 2020

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!

@stale stale bot added the Resolution: Stale Automatically closed due to inactivity label Jul 11, 2020
@mgol
Copy link
Contributor

mgol commented Jul 11, 2020

Still valid. Is there a way to disable the stale bot for this issue? It just spams & requires others to spam as well...

@stale stale bot removed the Resolution: Stale Automatically closed due to inactivity label Jul 11, 2020
@yordis
Copy link

yordis commented Jul 11, 2020

@mgol 🤷

react/.github/stale.yml

Lines 7 to 14 in 61dd00d

exemptLabels:
- "Partner"
- "React Core Team"
- "Resolution: Backlog"
- "Type: Bug"
- "Type: Discussion"
- "Type: Needs Investigation"
- "Type: Regression"

@stale
Copy link

stale bot commented Oct 12, 2020

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!

@stale stale bot added the Resolution: Stale Automatically closed due to inactivity label Oct 12, 2020
@silenceisgolden
Copy link

I believe this should not be marked as stale.

@bfsmith
Copy link

bfsmith commented Apr 3, 2021

Bumping before it gets marked as stale again.

@cdoremus
Copy link

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

@gaearon
Copy link
Collaborator

gaearon commented Jul 21, 2021

There's an active discussion in #11347 (comment) that you're welcome to contribute to if you care about this topic.

@stale
Copy link

stale bot commented Jan 9, 2022

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!

@stale stale bot added the Resolution: Stale Automatically closed due to inactivity label Jan 9, 2022
@silenceisgolden
Copy link

Hey bot, bump to the bump, to the bump bump bump. Maybe this will be the last bump though? 🤞🏻

@stale stale bot removed the Resolution: Stale Automatically closed due to inactivity label Jan 9, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests