facebook / react

The library for web and native user interfaces.
https://react.dev
MIT License
227.34k stars 46.37k forks source link

RFC: Plan for custom element attributes/properties in React 19 #11347

Open robdodson opened 6 years ago

robdodson commented 6 years ago

This is meant to address #7249. The doc outlines the pros and cons of various approaches React could use to handle attributes and properties on custom elements.

TOC/Summary

Background

When React tries to pass data to a custom element it always does so using HTML attributes.

<x-foo bar={baz}> // same as setAttribute('bar', baz)

Because attributes must be serialized to strings, this approach creates problems when the data being passed is an object or array. In that scenario, we end up with something like:

<x-foo bar="[object Object]">

The workaround for this is to use a ref to manually set the property.

<x-foo ref={el => el.bar = baz}>

This workaround feels a bit unnecessary as the majority of custom elements being shipped today are written with libraries which automatically generate JavaScript properties that back all of their exposed attributes. And anyone hand-authoring a vanilla custom element is encouraged to follow this practice as well. We'd like to ideally see runtime communication with custom elements in React use JavaScript properties by default.

This doc outlines a few proposals for how React could be updated to make this happen.

Proposals

Option 1: Only set properties

Rather than try to decide if a property or attribute should be set, React could always set properties on custom elements. React would NOT check to see if the property exists on the element beforehand.

Example:

<x-foo bar={baz}>

The above code would result in React setting the .bar property of the x-foo element equal to the value of baz.

For camelCased property names, React could use the same style it uses today for properties like tabIndex.

<x-foo squidInk={pasta}> // sets .squidInk = pasta

Pros

Easy to understand/implement

This model is simple, explicit, and dovetails with React’s "JavaScript-centric API to the DOM".

Any element created with libraries like Polymer or Skate will automatically generate properties to back their exposed attributes. These elements should all "just work" with the above approach. Developers hand-authoring vanilla components are encouraged to back attributes with properties as that mirrors how modern (i.e. not oddballs like <input>) HTML5 elements (<video>, <audio>, etc.) have been implemented.

Avoids conflict with future global attributes

When React sets an attribute on a custom element there’s always the risk that a future version of HTML will ship a similarly named attribute and break things. This concern was discussed with spec authors but there is no clear solution to the problem. Avoiding attributes entirely (except when a developer explicitly sets one using ref) may sidestep this issue until the browsers come up with a better solution.

Takes advantage of custom element "upgrade"

Custom elements can be lazily upgraded on the page and some PRPL patterns rely on this technique. During the upgrade process, a custom element can access the properties passed to it by React—even if those properties were set before the definition loaded—and use them to render initial state.

Custom elements treated like any other React component

When React components pass data to one another they already use properties. This would just make custom elements behave the same way.

Cons

Possibly a breaking change

If a developer has been hand-authoring vanilla custom elements which only have an attributes API, then they will need to update their code or their app will break. The fix would be to use a ref to set the attribute (explained below).

Need ref to set attribute

By changing the behavior so properties are preferred, it means developers will need to use a ref in order to explicitly set an attribute on a custom element.

<custom-element ref={el => el.setAttribute('my-attr', val)} />

This is just a reversal of the current behavior where developers need a ref in order to set a property. Since developers should rarely need to set attributes on custom elements, this seems like a reasonable trade-off.

Not clear how server-side rendering would work

It's not clear how this model would map to server-side rendering custom elements. React could assume that the properties map to similarly named attributes and attempt to set those on the server, but this is far from bulletproof and would possibly require a heuristic for things like camelCased properties -> dash-cased attributes.

Option 2: Properties-if-available

At runtime React could attempt to detect if a property is present on a custom element. If the property is present React will use it, otherwise it will fallback to setting an attribute. This is the model Preact uses to deal with custom elements.

Pseudocode implementation:

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

Possible steps:

Pros

Non-breaking change

It is possible to create a custom element that only uses attributes as its interface. This authoring style is NOT encouraged, but it may happen regardless. If a custom element author is relying on this behavior then this change would be non-breaking for them.

Cons

Developers need to understand the heuristic

Developers might be confused when React sets an attribute instead of a property depending on how they’ve chosen to load their element.

Falling back to attributes may conflict with future globals

Sebastian raised a concern that using in to check for the existence of a property on a custom element might accidentally detect a property on the superclass (HTMLElement).

There are also other potential conflicts with global attributes discussed previously in this doc.

Option 3: Differentiate properties with a sigil

React could continue setting attributes on custom elements, but provide a sigil that developers could use to explicitly set properties instead. This is similar to the approach used by Glimmer.js.

Glimmer example:

<custom-img @src="corgi.jpg" @hiResSrc="corgi@2x.jpg" width="100%">

In the above example, the @ sigil indicates that src and hiResSrc should pass data to the custom element using properties, and width should be serialized to an attribute string.

Because React components already pass data to one another using properties, there would be no need for them to use the sigil (although it would work if they did, it would just be redundant). Instead, it would primarily be used as an explicit instruction to pass data to a custom element using JavaScript properties.

h/t to @developit of Preact for suggesting this approach :)

Pros

Non-breaking change that developers can opt-in to

All pre-existing React + custom element apps would continue to work exactly as they have. Developers could choose if they wanted to update their code to use the new sigil style.

Similar to how other libraries handle attributes/properties

Similar to Glimmer, both Angular and Vue use modifiers to differentiate between attributes and properties.

Vue example:

<!-- Vue will serialize `foo` to an attribute string, and set `squid` using a JavaScript property -->
<custom-element :foo="bar” :squid.prop=”ink”>

Angular example:

<!-- Angular will serialize `foo` to an attribute string, and set `squid` using a JavaScript property -->
<custom-element [attr.foo]="bar” [squid]=”ink”>

The system is explicit

Developers can tell React exactly what they want instead of relying on a heuristic like the properties-if-available approach.

Cons

It’s new syntax

Developers need to be taught how to use it and it needs to be thoroughly tested to make sure it is backwards compatible.

Not clear how server-side rendering would work

Should the sigil switch to using a similarly named attribute?

Option 4: Add an attributes object

React could add additional syntax which lets authors explicitly pass data as attributes. If developers do not use this attributes object, then their data will be passed using JavaScript properties.

Example:

const bar = 'baz';
const hello = 'World';
const width = '100%';
const ReactElement = <Test
  foo={bar} // uses JavaScript property
  attrs={{ hello, width }} // serialized to attributes
/>;

This idea was originally proposed by @treshugart, author of Skate.js, and is implemented in the val library.

Pros

The system is explicit

Developers can tell React exactly what they want instead of relying on a heuristic like the properties-if-available approach.

Extending syntax may also solve issues with event handling

Note: This is outside the scope of this document but maybe worth mentioning :)

Issue #7901 requests that React bypass its synthetic event system when declarative event handlers are added to custom elements. Because custom element event names are arbitrary strings, it means they can be capitalized in any fashion. To bypass the synthetic event system today will also mean needing to come up with a heuristic for mapping event names from JSX to addEventListener.

// should this listen for: 'foobar', 'FooBar', or 'fooBar'?
onFooBar={handleFooBar}

However, if the syntax is extended to allow attributes it could also be extended to allow events as well:

const bar = 'baz';
const hello = 'World';
const SquidChanged = e => console.log('yo');
const ReactElement = <Test
  foo={bar}
  attrs={{ hello }}
  events={{ SquidChanged}} // addEventListener('SquidChanged', …)
/>;

In this model the variable name is used as the event name. No heuristic is needed.

Cons

It’s new syntax

Developers need to be taught how to use it and it needs to be thoroughly tested to make sure it is backwards compatible.

It may be a breaking change

If any components already rely on properties named attrs or events, it could break them.

It may be a larger change than any of the previous proposals

For React 17 it may be easier to make an incremental change (like one of the previous proposals) and position this proposal as something to take under consideration for a later, bigger refactor.

Option 5: An API for consuming custom elements

This proposal was offered by @sophiebits and @gaearon from the React team

React could create a new API for consuming custom elements that maps the element’s behavior with a configuration object.

Pseudocode example:

const XFoo = ReactDOM.createCustomElementType({
  element: ‘x-foo’,
  ‘my-attr’: // something that tells React what to do with it
  someRichDataProp: // something that tells React what to do with it
});

The above code returns a proxy component, XFoo that knows how to pass data to a custom element depending on the configuration you provide. You would use this proxy component in your app instead of using the custom element directly.

Example usage:

<XFoo someRichDataProp={...} />

Pros

The system is explicit

Developers can tell React the exact behavior they want.

Non-breaking change

Developers can opt-in to using the object or continue using the current system.

Idiomatic to React

This change doesn’t require new JSX syntax, and feels more like other APIs in React. For example, PropTypes (even though it’s being moved into its own package) has a somewhat similar approach.

Cons

Could be a lot of work for a complex component

Polymer’s paper-input element has 37 properties, so it would produce a very large config. If developers are using a lot of custom elements in their app, that may equal a lot of configs they need to write.

May bloat bundle size

Related to the above point, each custom element class now incurs the cost of its definition + its config object size.

Note: I'm not 100% sure if this is true. Someone more familiar with the React build process could verify.

Config needs to keep pace with the component

Every time the component does a minor version revision that adds a new property, the config will need to be updated as well. That’s not difficult, but it does add maintenance. Maybe if configs are generated from source this is less of a burden, but that may mean needing to create a new tool to generate configs for each web component library.

cc @sebmarkbage @gaearon @developit @treshugart @justinfagnani

bahrus commented 3 years ago

Hi @gaearon, I think this comment from @robdodson would be really helpful:

Because attributes must be serialized to strings, this approach creates problems when the data being passed is an object or array. In that scenario, we end up with something like:

The workaround for this is to use a ref to manually set the property. el.bar = baz}>

The documentation does mention refs as far as calling methods, but explicitly stating this as regards to passing objects in would go a long way in convincing me there's no desire for lock-in! Or maybe I missed it? I'm referring to this page.

Update: Okay, I guess I can create a PR. Maybe I should have all this time, rather than complaining about it passively.

matthewp commented 3 years ago

@josepharhar

Could you elaborate on "artificial delays"? Are you saying that we shouldn't wait for the custom element to be upgraded before throwing all JSX attributes into HTML/element attributes, especially when server rendering?

Yes, you 100% should not be waiting. When an element upgrades is not the concern of a templating library. It's the concern of the application and element author. The element author sets the contract with the application development on when an element needs to be defined. A robust element should be able to respond to being defined late. That means supporting state being set prior to upgrade; so looking for existing state in properties and/or attributes (latter being the most common).

josepharhar commented 3 years ago

Yes, that's what I'm saying. I don't like blocking attributes from server-side rendering.

Yes, you 100% should not be waiting.

Thanks for this feedback, that knocks out the third option in my doc.

Does anyone have opinions on the second option in my doc? The idea is that when a custom element upgrades, we would remove the HTML attribute from the element (via element.removeAttribute) and then assign it into the object's property in the case that the property-assigning heuristic ('prop' in element) is true. The alternative, which preact does now, is wait until the value being passed into that particular JSX attribute actually changes - then the value will be assigned to the element's property.

We could also do something in between, where we assign the same value to the property on upgrade but don't remove the attribute. Here's a table which may help me describe better:

rendering <my-custom-element myprop={'myvalue'} /> which has an object property for myprop Preact behavior attribute property
renderToString() or render() before upgrade 'myvalue' undefined
custom element just upgraded 'myvalue' undefined
value changed: myprop={'mynewvalue'} 'myvalue' 'mynewvalue'
Second option in my doc: Remove attribute and assign to property on upgrade attribute property
renderToString() or render() before upgrade 'myvalue' undefined
custom element just upgraded null 'myvalue'
value changed: myprop={'mynewvalue'} null 'mynewvalue'
"in between" option: Assign to property on upgrade attribute property
renderToString() or render() before upgrade 'myvalue' undefined
custom element just upgraded 'myvalue' 'myvalue'
value changed: myprop={'mynewvalue'} 'myvalue' 'mynewvalue'

Should react respond to custom element upgrades at all? Should react remove attributes on upgrade?

a way that works with the whole React feature set (including SSR), we would’ve implemented it years ago

This is a big deal - in an ideal world, we would support custom elements rendering themselves to HTML on the server, using features like declarative shadow DOM (the article even has example code for custom elements to hydrate themselves!). However, it is very unclear how we would do this:

Are you aware of any Web Component Libraries with React that run into issues. For example with https://lit.dev/ or any internal libraries at Google or on the Chromium team?

Thanks for bringing this up, I'll ask around

matthewp commented 3 years ago

@josepharhar You bring up a lot of questions but I'll just focus on the first. I think removing attributes and waiting on whenDefined could result in a FOUC. The element would render twice, once without the attributes (on upgrade) and again when React rerenders post-upgrade. Since it's state is different it can likely result in different render results.

If you leave the attributes alone the element has a chance to use those values to correctly render when it upgrades. Then in subsequent React renders (if they are to happen) can use the in heuristic.

This might sound strange, that your attributes are different than the property values, but this is in fact how most built-in elements work, they do not reflect internal changes onto attributes, attributes reflect initial state.

gaearon commented 3 years ago

I think removing attributes and waiting on whenDefined could result in a FOUC.

Could we do the "remove attributes + attach properties" thing on whenDefined?

josepharhar commented 3 years ago

I think removing attributes and waiting on whenDefined could result in a FOUC.

Could we do the "remove attributes + attach properties" thing on whenDefined?

Yeah I was proposing waiting until after the custom element has been upgraded, then removing the attribute and assigning the same value to the property if our heuristic determines that we should be using object properties.

I could imagine that it could result in something odd happening depending on how the custom element is written, but we would definitely do element.removeAttribute('myprop') and element['myprop'] = value in the same javascript task...

matthewp commented 3 years ago

@gaearon

Could we do the "remove attributes + attach properties" thing on whenDefined?

You could, does this solve some problem I'm not thinking of though? What do you do with builtin elements here? For example <progress max="50">, do you remove the attribute and start setting its property? progress will reflect property changes back into its attributes. Would a custom element doing the same mess with diffing or something?

matthewp commented 3 years ago

@josepharhar

I'd love to hear the reason why to remove the attribute, it's unclear to me. I can't think of a reason why you need to worry aobut the attr/prop being "out of sync". Many elements will reflect property changes back into the attribute, but some won't. In either case the element is the one that is deciding what the source-of-truth is. I'd be worried that by removing the attribute and the element then reflecting, it might mess with the diffing algo.

A simpler approach for implementing this might be to memoize how you set state on the element the first time. If the element was not upgraded and you used setAttribute, then continue using setAttribute for the lifetime of the element. If it was upgraded and you used a property setter, continue using the property setter.

This is nice for you because

1) You don't have to worry about attr/prop being out of sync, you're only setting one of them. and 2) You don't need to treat custom elements special. You don't even have to know that it's a custom element, this heuristic should work the same for builtin elements or HTMLUnknownElements.

gaearon commented 3 years ago

If the element was not upgraded and you used setAttribute, then continue using setAttribute for the lifetime of the element. If it was upgraded and you used a property setter, continue using the property setter.

This seems like the same kind of custom element could potentially work with one approach or with other approach but not necessarily both. So there is a risk that you'd either see or not see the issue depending on the timing. We generally prefer things to work the same way regardless of timing. It's better for custom element to not work at all (so that you notice the issue and address it) than for it to work sometimes.

josepharhar commented 3 years ago

You could, does this solve some problem I'm not thinking of though? What do you do with builtin elements here? For example , do you remove the attribute and start setting its property? progress will reflect property changes back into its attributes. Would a custom element doing the same mess with diffing or something?

I'd love to hear the reason why to remove the attribute, it's unclear to me. I can't think of a reason why you need to worry aobut the attr/prop being "out of sync". Many elements will reflect property changes back into the attribute, but some won't. In either case the element is the one that is deciding what the source-of-truth is. I'd be worried that by removing the attribute and the element then reflecting, it might mess with the diffing algo.

I agree, I think this is a good argument against using removeAttribute. The reason for using removeAttribute is that the state is left kind of funny looking when there is old state left in the attribute and new state in the property, but as you said attributes are (hopefully) more used for initial state only.

However, we still need to know if we should respond to upgrades - see the "Preact behavior" vs "Assign to property on upgrade" tables in my previous comment. (A benefit of not responding to upgrades like preact does is that it takes less code and is probably more performant within the framework...)

A simpler approach for implementing this might be to memoize how you set state on the element the first time. If the element was not upgraded and you used setAttribute, then continue using setAttribute for the lifetime of the element. If it was upgraded and you used a property setter, continue using the property setter.

I agree with Dan, I don't like this because it would significantly change the behavior based on whether the custom element was upgraded the first time you render() it, or if you used SSR - in those cases, we would be sticking to attributes based on timing.

ConradSollitt commented 3 years ago

If removeAttribute() would be used I would first make sure it doesn't exist in the observedAttributes() list assuming the element is already defined from whenDefined().

static get observedAttributes() {
  return ['name', 'color'];
}

attributeChangedCallback(name, oldValue, newValue) {
  // ...
}

@bahrus

We built web component wrappers around api libraries like eCharts. We are a financial company, so we use lots of grids and charts, so the ability to send objects (not strings) into and out of components (via events) is rather critical for us. Being able to load the components asynchronously is also quite important

I've created full SPA's using Web Components and a good solution I found in my case for this issue was to have the components pass data between themselves outside of any framework. Example, a <json-data> knows how to pass data down to <data-table> as an actual object and not a string once the data has downloaded. To keep it generic I define a value getter and setter on the components for data binding. That way the code works asynchronously and can handle data binding regardless of any main view library (React, Preact, Vue, etc). the main view libraries are then only aware of the element itself and doesn't cause any issues on my end.

ConradSollitt commented 3 years ago

I've checked Web Components I previously wrote and tested with React and found one issue that might make sense to add to a checklist of best practices if a topic like that is created in the future.

Basically attributeChangedCallback() can be fired at the time the element is created but before being attached to the DOM. This isn't an issue when using just including a Web Component on a web page but can cause issues with VDOM for complex components that need to modify the DOM.

The solution is for Web Component Authors to use element.isConnected prior to internal rendering. I've created an example on CodePen using Leaflet and OpenStreetMap.

The first map renders because it handles element.isConnected but the second map is blank and errors are logged to console from Leaflet.

The demo allows switching between React and Preact by commenting/uncommenting the desired library to test. Both React and Preact have the same result.

https://codepen.io/conrad-sollitt/pen/RwVLRqN?editors=1000

bahrus commented 3 years ago

The modified proposal Option 2.SubOption2.NoRemoval does sound better to me than Option 2.SubOption 3. I agree no to removing attributes (with a probably wrong caveat below). It might be a little better than the Preact solution, but...

My concerns are:

I guess that's my fundamental misunderstanding, and skepticism towards Reacts claim that the whole shebang needs/needed to be understood before implementing piecemeal, obvious (to me) improvements, while waiting for the harder problems to find a resolution. Regardless of hydration approaches, I believe the ability to specify some (non-string, potentially) properties should only be passed in on the client side (either originating on the server, or originating on the client), and not serialized to attributes, was there before SSR, and will remain after SSR. And that's precisely the functionality I've been sorely waiting for all these years. How can we specify such properties? (Sorry, option 3, or at least a core piece of it still sounds better to me).

I'm going to engage in some probably wrong mind reading -- maybe you need attributes for all properties, because they may contain guids that point to some functions / objects that are passed down separately? If that's the case, those attributes, and only those attributes, would make sense to remove. How do we specify that? Again, option 3 sounds like the way.

josepharhar commented 3 years ago

The modified proposal Option 2.SubOption2.NoRemoval does sound better to me than Option 2.SubOption 3. I agree no to removing attributes (with a probably wrong caveat below). It might be a little better than the Preact solution, but...

I apologize for making this confusing - you're referring to assigning into properties on upgrade or hydration, right?

How much of a performance impact does it add by adding all these upgrade listeners?

Great question

Why wait for the upgrade? Why not just pass in the values as soon as it is known?

We can't use the attribute vs property heuristic until the custom element is upgraded

And I understand from parallel work being done by the lit team, that there are some performance benefits of coordinating the order in which components start actively responding to state changes (hence their defer-hydration proposal)

Could you link to this?

but as far as I know, no need to throttle when the values should be passed

Right, which is why we have determined that we should continue to be setting HTML attributes in SSR and before upgrade

But if we are imposing any performance penalty to accommodate web components that need a little fine tuning so they can properly absorb properties passed in ahead of time, I'm kind of opposed. If data being passed in is done in a particular order, in order to reduce page reflows, or other beneficial reasons like that, I am in favor of waiting for that reason.

If we pass in properties immediately after upgrade instead of waiting for the value itself to change like preact does, then it would make custom elements which are only looking at properties for complex data like objects which doesn't work well in attributes work better, right...?

How are you matching names of attributes with their properties? I suppose it might be safe to assume either a dash or no dash, and that might work in all cases, but I just want to understanding the think / algorithm there. The examples are all lower case, so they match. I hope React isn't imposing that requirement?

I don't think I understand your question... when you say "name", are you talking about the JSX attribute name, which is used as the name of the attribute or property...? Which examples are lower case?

it won't make much sense for a significant number of properties, but not all the properties, to reflect as attributes during SSR

What differentiation between "significant number" and "all" are you making?

but some objects / functions can't be serialized?

In preact, functions won't serialize and objects will serialize to "[Object object]". Today, in react, object also serialize to "[Object object]" and functions get toString()'d. See the renderToString section of my test site.

I believe the ability to specify some (non-string, potentially) properties should only be passed in on the client side (either originating on the server, or originating on the client), and not serialized to attributes, was there before SSR, and will remain after SSR

I suppose that we could treat values which are object specially and wait until custom element upgrade and hydration to assign them...?

bahrus commented 3 years ago

I apologize for making this confusing - you're referring to assigning into properties on upgrade or hydration, right?

I guess I question the premise for this.

  1. Web components that follow best practices do not require upgrading before assigning into properties, on the client. Are we on the same page there?
  2. On the client, Preact only has to wait for the upgrade, because Preact doesn't "own" JSX, hence couldn't make up syntax for us to specify which bindings were meant for string attributes, vs object properties, so it tried its best. React "owns" JSX, but unfortunately seems too reluctant to allow us to specify that explicitly. So yes, within those same constraints, I'm referring to assigning properties, which due to that intransigence means we have to wait. I'm suggesting we are making sacrifices in design to accommodate the rejection of a proposal (option 3) with no explanation (to my knowledge) ever provided. I can live with that, but the truth shall set you free, and I will stop harping on option 3 as soon as I feel satisfied this reality is understood (or perhaps I'm missing something?).

Great question

If it's a great question, perhaps we should look more closely at option 3? :-)

We can't use the attribute vs property heuristic until the custom element is upgraded

Because option 3 was rejected. Agreed?

Could you link to this?

https://github.com/webcomponents/community-protocols/issues/16 https://github.com/webcomponents/community-protocols/issues/7

If we pass in properties immediately after upgrade instead of waiting for the value itself to change like preact does, then it would make custom elements which are only looking at properties for complex data like objects which doesn't work well in attributes work better, right...?

Due to syntax limitations JSX is imposing, this statement probably makes sense.

I don't think I understand your question... when you say "name", are you talking about the JSX attribute name, which is used as the name of the attribute or property...? Which examples are lower case?

In the example I gave of the UI5 web component, they have a property called valueState and an attribute called value-state, that are intimately tied together. Maybe I'm revealing my ignorance about JSX. How does JSX pair these two differing names together with a single binding? I can imagine some assumptions are made to do that, I just want to understand what those assumptions are.

What differentiation between "significant number" and "all" are you making?

Yes, that was vague. A web component might provide the ability to pass in function callbacks as a property, pass in binary data as another property, pass in another custom element for a third property, etc. I would expect such properties wouldn't have corresponding attributes, (because web components assume attributes can be interpreted in some meaningful way) and it only makes sense to pass these in the client, and SSR would appear to have little to nothing to say about it, little value-add. Support for this feature, it seems to me, should have been added by React years ago, and did not need to wait for SSR to be all figured out, because, as I said, SSR has little to no direct connection to it, as far as I can see. That would have made me a happy camper. But I've been repeatedly reading that it has to wait for a full blown SSR solution, and I've never understood that. Regardless, we are hopefully going to provide support for that with this solution, I'm hoping? Not all properties have corresponding attributes is what I'm really saying, and we need a way to pass things into these properties on the client, using JSX binding. Agreed?

effulgentsia commented 3 years ago

Sorry if this was discussed already, but I don't see it from a quick skim...

According to https://custom-elements-everywhere.com/, Preact's approach distinguishes between a data value that's an object or array from a data value that's a primitive. For the former, it always passes as a property (so on SSR: not rendered as an attribute and instead hydrated as a property on the client), which means that it can set this as a property on the client without caring whether the element is upgraded or not. Meanwhile, for primitive values, it uses the "property if a setter is defined, otherwise attribute" rule, which means it sets as an attribute during SSR or on the client prior to custom element upgrade and as a property after custom element upgrade.

What are the cons to React following this same approach? I think the only cons raised relate to a custom element that has a property setter that expects a primitive value but during upgrade doesn't initialize that property value from the same-named (adjusted for camelCase to kebab-case) attribute value? However, that seems like a bug with the custom element, so not sure how much React should try to accommodate for it, especially when several other popular JS libraries (Preact, Svelte, others) don't.

bahrus commented 3 years ago

The significant con is for custom elements that are loaded asynchronously, if the component only renders once, and the component hasn't been upgraded yet, the value is passed in as an attribute string. It might (I don't recall) recover from that on subsequent renders (and I think that was discussed in some of the research @josepharhar has provided.)

Option 2.SubOption2.NoRemoval does, I think, improve this (hooray!) by waiting for the upgrade, with few, if any, downsides that I can see. The only downside might be the performance costs of the awaits (probably small?)

effulgentsia commented 3 years ago

However, that con (elements loaded asynchronously receiving primitive values as attribute values until they are upgraded) is not in any way a con for custom elements that initialize their primitive properties from attribute values. What is a legitimate (non-buggy) example of a custom element with a property setter that can receive a primitive value that doesn't itself initialize that property value from the corresponding attribute value?

bahrus commented 3 years ago

Preact (and I think React) works fine with primitive string values. But imagine working with a chart, that needs either JSON attributes, or object properties passed in. I think that's what this whole RFC is about. I should have spelled that out in my brief summary.

So I am taking issue with your statement:

For the former, it always passes as a property

Not so for asynchronously loaded components.

effulgentsia commented 3 years ago

On https://custom-elements-everywhere.com/, it says:

Preact uses a runtime heuristic to determine if it should pass data to Custom Elements as either properties or attributes. If a property is already defined on the element instance, Preact will use properties, otherwise it will fallback to attributes. The exception to this rule is when it tries to pass rich data, like objects or arrays. In those instances it will always use a property.

Is that incorrect?

bahrus commented 3 years ago

That statement is correct, but the key word there is "If". For unknown elements still awaiting the download of the JS needed to register the custom element, Preact sees no property already defined, and assumes it is an attribute, and sets that attribute to "[Object object]", so the custom element has no data to work with when it upgrades, at least until the next render.

effulgentsia commented 3 years ago

I meant the part that says:

The exception to this rule is when it tries to pass rich data, like objects or arrays. In those instances it will always use a property.

bahrus commented 3 years ago

Oh, true, yes, that probably should have been softened, I agree.

bahrus commented 3 years ago

Yeah, that is definitely not what it did when I worked with it (version 8). I have not checked version 10, but I assume from the discussions and @josepharhar's findings, that it is still the case, and that that sentence is inaccurate. Let me review @josepharhar's findings again...

bahrus commented 3 years ago

Good point @effulgentsia, @josepharhar, can you confirm whether that statement is accurate or not? Does Preact now always assume anything that isn't a primitive should be assigned as a property, on the client?

bahrus commented 3 years ago

https://preactjs.com/guide/v10/web-components/

JSX does not provide a way to differentiate between properties and attributes. Custom Elements generally rely on custom properties in order to support setting complex values that can't be expressed as attributes. This works well in Preact, because the renderer automatically determines whether to set values using a property or attribute by inspecting the affected DOM element. When a Custom Element defines a setter for a given property, Preact detects its existence and will use the setter instead of an attribute.

bahrus commented 3 years ago

Assuming complex objects are properties on the client actually makes good sense. I do wonder why Preact didn't adopt that rule (which I'm maybe 90% confident now they didn't).

https://github.com/preactjs/preact/blob/master/src/diff/props.js#L13

ConradSollitt commented 3 years ago

@effulgentsia

However, that con (elements loaded asynchronously receiving primitive values as attribute values until they are upgraded) is not in any way a con for custom elements that initialize their primitive properties from attribute values. What is a legitimate (non-buggy) example of a custom element with a property setter that can receive a primitive value that doesn't itself initialize that property value from the corresponding attribute value?

Here is an example:

https://www.dataformsjs.com/examples/web-components-with-react.htm#/data

Both the data page and image gallery do something similar. A <json-data> component asynchronously downloads data from a JSON service after being created from React and then they use a value property to pass data from the JSON service to a child Custom Element. In this case a <data-table> custom element. There is no corresponding value observable attribute because it wouldn't make sense to include in the API.

Here is the JSX for that part of the app:

function DataPage() {
    return (
        <json-data url="https://www.dataformsjs.com/data/geonames/countries" load-only-once>
            <is-loading template-selector="#loading-screen"></is-loading>
            <has-error template-selector="#error-screen"></has-error>
            <is-loaded class="flex-col">
                // ...
                <data-table
                    data-bind="countries"
                    highlight-class="highlight"
                    labels="Code, Name, Size (KM), Population, Continent"
                    table-attr="is=sortable-table,
                                data-sort-class-odd=row-odd,
                                data-sort-class-even=row-even">
                </data-table>
            </is-loaded>
        </json-data>
    );
}

Then to see the data table change run this from Dev Tools.

document.querySelector('data-table').value = [{field1:'abc', field2:123}]
ConradSollitt commented 3 years ago

@effulgentsia

Here is another demo for your earlier question: https://codepen.io/conrad-sollitt/pen/yLbzraj?editors=1000

See comments at top of the HTML. You can switch between React and Preact by commenting/un-commenting the library you want to test.

The <data-table> is populated by setting the value property. Both React and Preact work when using refs and manually setting the value however only Preact works when setting the records directly from JSX like this <data-table value={records}></data-table>.

For a large comparison of differences see this page: https://josepharhar.github.io/react-custom-elements/

That page was created by @josepharhar for this issue and listed in https://docs.google.com/document/d/1PWm94eCKZ9OBe91X-3ZHIBxn_w0E92VrzSImTEmXPZc/edit

effulgentsia commented 3 years ago

@ConradSollitt: Thank you for those examples. However, for both of them, the value of value is an array. My suggestion from earlier was to do what https://custom-elements-everywhere.com/ says that Preact does (though apparently, not what Preact actually does), and to always set as a property when the value is an array or object, and to only do the "property if not-SSR-and-element-is-upgraded-and-element-has-a-setter-for-the-property, otherwise attribute" logic for primitive (i.e., string, number, boolean) values. So my question was:

What is a legitimate (non-buggy) example of a custom element with a property setter that can receive a primitive value that doesn't itself initialize that property value from the corresponding attribute value?

josepharhar commented 3 years ago

Web components that follow best practices do not require upgrading before assigning into properties, on the client. Are we on the same page there?

Thanks for the link! Based on @matthewp’s comment, it sounds like this is an issue people might still run into… Also, I still don’t see how we would ever be setting properties instead of attributes before upgrading with what is on the table.

On the client, Preact only has to wait for the upgrade, because Preact doesn't "own" JSX, hence couldn't make up syntax for us to specify which bindings were meant for string attributes, vs object properties, so it tried its best. React "owns" JSX, but unfortunately seems too reluctant to allow us to specify that explicitly. So yes, within those same constraints, I'm referring to assigning properties, which due to that intransigence means we have to wait. I'm suggesting we are making sacrifices in design to accommodate the rejection of a proposal (option 3) with no explanation (to my knowledge) ever provided. I can live with that, but the truth shall set you free, and I will stop harping on option 3 as soon as I feel satisfied this reality is understood (or perhaps I'm missing something?).

I hear your interest in declaratively differentiating between attributes and properties.

Because option 3 was rejected. Agreed?

Yep

webcomponents/community-protocols#16 webcomponents/community-protocols#7

Thanks for the links! I’m not sure I fully understand how that applies here… should react look for a defer-hydration attribute and avoid setting properties instead of attributes on it until its removed or something…?

In the example I gave of the UI5 web component, they have a property called valueState and an attribute called value-state, that are intimately tied together. Maybe I'm revealing my ignorance about JSX. How does JSX pair these two differing names together with a single binding? I can imagine some assumptions are made to do that, I just want to understand what those assumptions are.

Ah, I wasn’t aware of this concept - what I’m proposing, as well as preact’s current behavior, don’t allow for a way to have one JSX attribute apply to both an attribute named “value-state” and a property named “valueState”.

Yes, that was vague. A web component might provide the ability to pass in function callbacks as a property, pass in binary data as another property, pass in another custom element for a third property, etc. I would expect such properties wouldn't have corresponding attributes, (because web components assume attributes can be interpreted in some meaningful way) and it only makes sense to pass these in the client, and SSR would appear to have little to nothing to say about it, little value-add. Support for this feature, it seems to me, should have been added by React years ago, and did not need to wait for SSR to be all figured out, because, as I said, SSR has little to no direct connection to it, as far as I can see. That would have made me a happy camper. But I've been repeatedly reading that it has to wait for a full blown SSR solution, and I've never understood that. Regardless, we are hopefully going to provide support for that with this solution, I'm hoping? Not all properties have corresponding attributes is what I'm really saying, and we need a way to pass things into these properties on the client, using JSX binding. Agreed?

Yes. Preact does this like you want, right? And if we also reconsidered assigning to a property instead of an attribute when the custom element upgrades, then it will improve the situation when react renders before the custom element is upgraded, right…?

Sorry if this was discussed already, but I don't see it from a quick skim...

According to https://custom-elements-everywhere.com/, Preact's approach distinguishes between a data value that's an object or array from a data value that's a primitive. For the former, it always passes as a property (so on SSR: not rendered as an attribute and instead hydrated as a property on the client), which means that it can set this as a property on the client without caring whether the element is upgraded or not. Meanwhile, for primitive values, it uses the "property if a setter is defined, otherwise attribute" rule, which means it sets as an attribute during SSR or on the client prior to custom element upgrade and as a property after custom element upgrade.

What are the cons to React following this same approach? I think the only cons raised relate to a custom element that has a property setter that expects a primitive value but during upgrade doesn't initialize that property value from the same-named (adjusted for camelCase to kebab-case) attribute value? However, that seems like a bug with the custom element, so not sure how much React should try to accommodate for it, especially when several other popular JS libraries (Preact, Svelte, others) don't.

Based on a test I just made, it looks like preact does not distinguish between strings and objects: https://jsfiddle.net/jarhar/h1qtbwa3/

The significant con is for custom elements that are loaded asynchronously, if the component only renders once, and the component hasn't been upgraded yet, the value is passed in as an attribute string. It might (I don't recall) recover from that on subsequent renders (and I think that was discussed in some of the research @josepharhar has provided.)

Option 2.SubOption2.NoRemoval does, I think, improve this (hooray!) by waiting for the upgrade, with few, if any, downsides that I can see. The only downside might be the performance costs of the awaits (probably small?)

yep!

ConradSollitt commented 3 years ago

@effulgentsia Opps, should have paid attention to the primitives part.

Just checked my code and from the first demo I have a <markdown-content> component. https://www.dataformsjs.com/examples/web-components-with-react.htm#/markdown

It has the following API:

Rather than using a value HTML attribute it is designed to optionally handle inline markdown:

<markdown-content><script type="text/markdown"># Hello World</script></markdown-content>

One thing to keep in mind when it comes to Web Components is that they are very flexible in how they are defined. Kind of like old jQuery plugins - the same feature set could be defined in completely different manners based on the author and intended audience or usage.

Source code is here: https://github.com/dataformsjs/dataformsjs/blob/master/js/web-components/markdown-content.js

josepharhar commented 3 years ago

@ConradSollitt: Thank you for those examples. However, for both of them, the value of value is an array. My suggestion from earlier was to do what https://custom-elements-everywhere.com/ says that Preact does (though apparently, not what Preact actually does), and to always set as a property when the value is an array or object, and to only do the "property if not-SSR-and-element-is-upgraded-and-element-has-a-setter-for-the-property, otherwise attribute" logic for primitive (i.e., string, number, boolean) values. So my question was:

What is a legitimate (non-buggy) example of a custom element with a property setter that can receive a primitive value that doesn't itself initialize that property value from the corresponding attribute value?

This sounds promising... assuming the custom element doesn't suffer from this probem: https://github.com/facebook/react/issues/11347#issuecomment-879911564

Let me see if I understand correctly: condition assign to attribute or property?
not upgraded or SSR, value is object/array/function property (or nothing if SSR)
not upgraded or SSR, value is string/number attribute
upgraded, value is object/array/function property
upgraded, value is string/number, 'propname' in element property
upgraded, value is string/number, !('propname' in element) attribute
ConradSollitt commented 3 years ago

@josepharhar Take a look at the comment right above your last one: https://github.com/facebook/react/issues/11347#issuecomment-885208426

In the example I pass a string/primitive but don't include it as an HTML attribute. Reason being I felt it made more sense to support inline markdown rather than passing it as an HTML property.

Of course though being the author of the component if either React or Preact change behavior to always set primitives as HTML attributes then I would just update the component to support it. I checked as Web Components I wrote and that was the only example of a primitive type using a JS property without a related HTML attribute so my guess is it's not common.

josepharhar commented 3 years ago

Of course though being the author of the component if either React or Preact change behavior to always set primitives as HTML attributes then I would just update the component to support it. I checked as Web Components I wrote and that was the only example of a primitive type using a JS property without a related HTML attribute so my guess is it's not common.

Unless I missed something someone said, I don't think that always using attributes instead of properties for primitive types is on the table...

ConradSollitt commented 3 years ago

Unless I missed something someone said, I don't think that always using attributes instead of properties for primitive types is on the table...

Yeah, looks that way. If it were it would be a someone quick fix for Web Component authors that support React and Preact.

bahrus commented 3 years ago

Thanks for the link! Based on @matthewp’s comment, it sounds like this is an issue people might still run into… Also, I still don’t see how we would ever be setting properties instead of attributes before upgrading with what is on the table.

I think what @matthewp was saying wasn't too far off from your summary, but a shade different. There are somewhat esoteric ways to make web components upgrade properly, but they do upgrade properly if sufficient care is taken, and that's the responsibility of the web component community to take care of.

I’m not sure I fully understand how that applies here… should react look for a defer-hydration attribute and avoid setting properties instead of attributes on it until its removed or something…?

It seems like there should be a little cross-awareness here of what is happening. No direct impact on React (but React should allow attributes to flow through from the server, which was decided yesterday, so that's great).

Ah, I wasn’t aware of this concept - what I’m proposing, as well as preact’s current behavior, don’t allow for a way to have one JSX attribute apply to both an attribute named “value-state” and a property named “valueState”.

So there might or might not be an issue here. If I'm understanding the direction React is going with SSR -- if it is going full swing towards client-side + server-side components, and quickly deprecating other approaches, I think that is an elegant way of giving developers the ability to shape how much they want to utilize attributes, if at all. Developers can choose to not add any binding on the server-side components, and add to the client-side components, without incurring any unnecessary bandwidth penalties. Likewise, users of libraries like UI5 could specify the correct name for the client-side property vs the server-side attribute, and that wouldn't seem like unnecessary busy work, as the markup will be cleaner and closer to the reality of what's happening, so the extra work seems worth it to me. I hope what I just said is accurate. If it is, then that seems to resolve my concerns here.

If, however, React is going to promote the existing approaches in parallel for a long, sustained time, I think I do see an issue here, which I kind of dismissed as an issue earlier, but is kind of a serious issue -- ending up with two sets of attributes for each property, and the apparent inability to say "I need this property to bind on the client, but please don't send any value as an attribute. It also seems quite clumsy to have to specify two bindings, one for the attribute, one for the property, as it would make tags seem rather cluttered.

bahrus commented 3 years ago

It seems to me React should be extra motivated to provide a solution that accommodates web components that use dashes in their attribute names -- being that at least one member of the team thinks all non global attributes should have dashes. Right?

ConradSollitt commented 3 years ago

@bahrus Do you know of a scenario where dashes do not work with attribute names for React with Web Components? I am not a team member or employee of FB but since I author Web Components (and React Components) I'm interested in the issue and if there are problems I'm unaware of. The demo from my earlier comment https://github.com/facebook/react/issues/11347#issuecomment-885157069 uses React (Preact version also exists of the demo and they share JSX) and many of the Web Components in the demo contain dashes and I found no issues.

bahrus commented 3 years ago

@ConradSollitt

I think possibly that where we are at this point, Option2.SubOption2.NoRemove, to some degree we are moving on from "does not work" to the question of "does it work really well?"

@josepharhar's comment:

Ah, I wasn’t aware of this concept - what I’m proposing, as well as preact’s current behavior, don’t allow for a way to have one JSX attribute apply to both an attribute named “value-state” and a property named “valueState”.

...leads me to believe we have an issue, where things may "work", but if you force your browser to throttle things and play things in very slow motion, I think there may be some issues, or if we ask the question "have we optimized the way this work sufficiently?" the answer might be no. I really want to endorse a solution, as I want this issue resolved, so much so that I will be happy with some compromises. But I'm trying to bring up issues, in case they are issues that haven't been considered yet.

One unresolved question is whether SSR should behave kind of identically to CSR. Your example demo is CSR, where there is a tiny concern I have. My last two posts were actually more focused on SSR, where those concerns grow larger.

@josepharhar seems quite willing to engage in considering options where SSR behaves differently from CSR. So that appears to be on the table still, which is a good thing.

For example, having properties with complex objects do nothing during SSR but only pass in property values on the client is a reasonable choice to make, if we must rule out option 3 and go with one and only one behavior for this scenario.
@josepharhar is considering that possibility here Unfortunately, this blocks out the ability, to sometimes say "hey, I would actually like to JSON.stringify this property during SSR if I may, because I've found that in this particular scenario it hydrates more quickly. Oh, and also, strange as it sounds, I have style associated with the presence of the attribute associated with that property. Likely scenario? Probably not. But one way or the other, Option 2 is going to block developers from making certain choices, I think, some of them reasonable choices.

It seems to me React really wants to "keep things simple" and have only one binding syntax for developers to have to grapple with. I understand the appeal. It's like a car with automatic transmission. Even though the reality is that behind the scenes, there are still constantly shifting gears, the driver can be oblivious to this fact. But even cars still have to have the ability to go in reverse, and shifting into higher gears when going downhill for a sustained period of time. In my mind, the developer, and the user, would still benefit from some (maybe not frequently used) ways of "shifting gears" when absolutely necessary. One such case, I think, is working with compound name properties, which link up with attributes with dashes in the names. This ought to be a high priority concern for the React team, if they are indeed sincere about their concerns about us using future global attribute names, and their desire to see more hyphens in custom element related aspects, like attributes and event names.

Let's start with CSR, which we both understand more deeply as both of us have used that exclusively, I think.

Say I have a property, irrationalNumber, with corresponding attribute "irrational-number" which will require rapid updates on the client, being passed in different values from a React component. Typical values might be Math.PI, or Math.E. There's a significant cost to using attributes for doing this, as this would require repeated float.parse's, which has a cost. Why incur that cost, if the React component already has the float number in memory?

But suppose I need styling to be affected by whether or not a value has been passed in.

So I will need to design my web component in such a way that the web component will reflect the value to the attribute, when the value of the property is passed in. This behavior, it has been rightly pointed out, is not typical behavior for native, built-in elements. Web component authors are encouraged to use pseudo-state, which aligns better with how native-born elements behave, but which doesn't yet have cross-browser support.

This scenario is actually much more important when it comes to SSR (but easier to understand perhaps with CSR).

So during SSR rendering of the component, it should render:

<x-foo irrational-number=3.1>
...my light children
</x-foo>

Or with CSR, during the first round of rendering, before the component has upgraded, it would also be helpful to add that attribute, so styling can immediately kick in. (If the upgrade has already taken place, the component could be passed in the property value, and reflect to the attribute / pseudo-state as needed, so React doesn't need to care about this at that point).

But on the client, during hydration, or (after waiting for the element to upgrade due to the constraints option 2 imposes) we need to pass in myXFoo.irrationalNumber = 3.1415926535897932384626433832795028841971693993751058209749445923078164062862089986280348253421170679

It appears I cannot express this with JSX as it stands, according to @josepharhar. Or I can, by using duplicate bindings

<x-foo irrationalNumber={myNumber} irrational-number={myNumber}/>

which would produce a very confusing situation for the web component developer -- having to discard approximate or expensive string attribute updates when more precise property values are being passed in. And on the SSR side, it means possibly duplicate attributes being sent.

Which all seems ironic -- React's team claims part of their foot dragging is because of lack of enthusiasm for supporting custom attributes without dashes, and yet, not expanding JSX appears to be pushing developers hard in the direction of avoiding dashes in attribute names.

But to be fair, possibly React's client-side vs server-side components might be a good solution, if it is expected that developers will typically duplicate their JSX syntax across both files, with minor variations, and if other solutions will quickly be deprecated. If either of those if's break down, then I think Option 2 is insufficient, and failure to enhance JSX would mean the React team is guilty of saying one thing, and yet doing another. Still, if it's an improvement over what we have (which Option2.SubOption2.NoRemove appears to be) I'll take it. Just saying.

bahrus commented 3 years ago

I should add, it might be possible to solve the issue of compoundName properties pairing with dashes in attribute names with Option2, if JSX makes some assumptions, does a little fishing around for the corresponding attribute name, or something. But that should be spelled out.

effulgentsia commented 3 years ago

For some background to @bahrus's comment, part of the challenge with respect to whether multiword attribute names have dashes or not comes from inconsistencies within HTML itself as well as across web component helper libraries. For example:

Meanwhile, most web component helper libraries allow you to override the default mapping for any given property, and so, for example, some PatternFly elements prefix some attribute names, but not the corresponding property names, with "pfe-".

I can think of 3 options for how React might want to deal with this:

  1. Follow Preact's lead and not deal with this at all. Meaning, do not convert JSX attribute names at all when rendering an HTML attribute. Since HTML attribute names are case-insensitive, this effectively maps a fooBar property to a foobar attribute, whether React calls toLowerCase() when rendering or not. Developers using components that use a different name for the attribute than for the property (whether that's including dashes, or a prefix, or whatever) then must make a choice: they can either use the attribute name in their JSX, in which case all updates will run through setAttribute() (just like how React works today), or they can use the property name in their JSX, in which case the wrong attribute name gets rendered during SSR or before hydration, but so long as React also sets the property quickly after hydration or custom element upgrade, this might be acceptable to the developer.
  2. React could assume that any JSX attribute with an uppercase letter in it is a property, without needing the custom element to upgrade first in order to ask it. After all, if it's not a property, why would the JSX author include an uppercase letter in it? React could then SSR it as an attribute as well, but set the property immediately upon hydration without waiting for the element to upgrade. This might help improve things a bit for the application, but it wouldn't address every use-case (such as single-word property names that map to a different attribute name or perhaps some FOUC conditions).
  3. React could mint a new namespaced JSX attribute name, perhaps something like react:htmlAttributes. This would be an object keyed on the JSX attribute name. It could be empty or only include the JSX attributes which need non-default behavior. The value for each JSX attribute name could be either true, false, or a string. True could mean to always set it as an attribute (never check for a property). False could mean to always set it as a property (never render or set it as an attribute). And a string would mean to set it as a property (using the JSX attribute name) on the client, but during SSR to render it as an attribute with the name specified in that string. For any JSX attributes not in this object, apply the default behavior (1 or 2 above). Developers wouldn't need to know or care about the react:htmlAttributes JSX attribute until they run into a problem with React's default behavior, and then for those cases, this would give them a way to resolve such a problem.

React's maintainers might dislike this comment's item 3 for some of the same reasons they dislike this issue's original "Option 3". Especially if there's no prior use for a react: namespace. However, there is a prior suggestion for moving React's other special attributes (like key and ref) to a namespace.

josepharhar commented 3 years ago

Thanks for the feedback!

I don't have any feedback about the "valueState" vs "value-state" problem right now.

I'd like to bring up another topic: custom event listeners.

Preact currently implements event listeners by doing this:

This behavior is applied to all elements in Preact, not just custom elements. In React, I propose we do something very similar but only for custom elements where the JSX name is not already a reserved React event handler name - those should still go through the React event system.

I also think it would make sense to look at the type of the value - if it is a function, then do the Preact logic. If it's something else, then fall back to the regular attribute vs property behavior. I imagine there are attributes or properties out there which happen to start with "on", right...?

bahrus commented 3 years ago

My view:

Hopefully someday React will incorporate (a little of?) Option 3, including the ability to specify whether an JSX attribute is a client-side-only property, and that will allow for properties that start with "on".

gaearon commented 3 years ago

Initial implementation work has started: https://github.com/facebook/react/pull/22184. Huge thanks @josepharhar for driving this.

gaearon commented 2 years ago

We've merged https://github.com/facebook/react/pull/22184 into main behind a flag.

This means that the new behavior, described in https://github.com/facebook/react/pull/22184#issuecomment-987510813, has landed into the @experimental npm release of react and react-dom. Here is an example showing that assigning properties and event handlers works now: https://codesandbox.io/s/shy-tdd-8b4tq?file=/src/App.js

The behavior has landed in the @experimental builds, but we haven't yet reached a final decision on the behavior for the 18 release. You can start using it right away in the @experimental channel, but whether it lands in @18 or gets deferred to another major release depends on the amount of real-world testing the @experimental build will receive from the community. If we can get apps using WCs widely try it in practice in the new few weeks and provide feedback on the new behavior in the @experimental build, we might be able to include it in 18. If we don't get enough feedback by that point, we might have to keep it @experimental-only until we have more confidence that the new behavior doesn't have big flaws.

If we end up deferring it, this would be understandably frustrating, so I want to clarify that:

  1. You can still start using it right away if you're comfortable with @experimental releases.
  2. We intend the next major after 18 to be fast-follow with a number of related breaking changes.
  3. This wouldn't be the only thing to get deferred. In fact, we've wanted clean up the DOM bindings for years now, but didn't want to churn the community for each change in isolation. These changes include forbidding javascript: URLs (for security), removing input attribute/property mirroring (to fix bugs), removing legacy context (to reduce bundle size), removing object-assign polyfill (to reduce bundle size), removing deprecated lifecycle names (which we initially planned to do in 17, then moved to 18, and then to 19). So this isn't a case of us "not caring" about some feature (we care about all of these!) but about making sure that the grouping makes sense and that any churn gets batched.

So please give the @experimental release a try and let us know your experiences!

weihong1028 commented 2 years ago

Nice,thank you!                               ——weihong

gaearon commented 2 years ago

also can i just say @josepharhar has done some heroic effort here in scoping this out, weighing tradeoffs, researching the landscape, proposing the direction and implementing it. huge props 💜

justinfagnani commented 2 years ago

@gaearon thanks for the update! And thanks so much for your hard work @josepharhar!

For the example you linked above, do you know why the event handler doesn't appear to be called? Is it the demo, or something in the event support?

Kim-Andersen commented 2 years ago

This is great news. Thanks for the effort to everybody involved.

About the example code; the event handler, thus the console.log, is never called on my end too.

Kim

ons. 8. dec. 2021 kl. 18.30 skrev Justin Fagnani @.***>:

@gaearon https://github.com/gaearon thanks for the update! And thanks so much for your hard work @josepharhar https://github.com/josepharhar!

For the example you linked above, do you know why the event handler doesn't appear to be called? Is it the demo, or something in the event support?

— You are receiving this because you are subscribed to this thread. Reply to this email directly, view it on GitHub https://github.com/facebook/react/issues/11347#issuecomment-989021700, or unsubscribe https://github.com/notifications/unsubscribe-auth/ABBO5LVKET3HAG5MSOG7ZALUP6I4VANCNFSM4EAPOZTQ . Triage notifications on the go with GitHub Mobile for iOS https://apps.apple.com/app/apple-store/id1477376905?ct=notification-email&mt=8&pt=524675 or Android https://play.google.com/store/apps/details?id=com.github.android&referrer=utm_campaign%3Dnotification-email%26utm_medium%3Demail%26utm_source%3Dgithub.