Wrapper triggers the setter on each re-render
Nobody has claimed this yet.
- Dominant language
- TypeScript
- Stars
- 21.8k
- Forks
- 1.1k
- Avg merge
- 18h 25m
- Merged PRs (30d)
- 2
Description
### Which package(s) are affected?
React (@lit/react)
### Description
Lit React wrapper triggers the web component setter on each re-render even if the value did not change since the last render.
In comparison, without wrappers, both React 18 and React 19 only re-trigger the web component setter if the value changed.
Lit React wrapper is inconsistent with React behavior.
https://github.com/lit/lit/blob/fbda6d7b42b8acd19b388e9de0be3521da6b58bb/packages/react/src/create-component.ts#L265
### Reproduction
Open console. See that wrapper triggers setter on each re-render. Without wrapper, setter only triggered if value changed:
https://lit.dev/playground/#sample=examples%2Freact-basics&project=W3sibmFtZSI6ImFwcC50c3giLCJjb250ZW50IjoiaW1wb3J0IFJlYWN0IGZyb20gJ2h0dHBzOi8vZXNtLnNoL3JlYWN0QDE4JztcbmltcG9ydCB7Y3JlYXRlUm9vdH0gZnJvbSAnaHR0cHM6Ly9lc20uc2gvcmVhY3QtZG9tQDE4L2NsaWVudCc7XG5pbXBvcnQge2NyZWF0ZUNvbXBvbmVudH0gZnJvbSAnQGxpdC9yZWFjdCc7XG5pbXBvcnQge0RlbW9HcmVldGluZyBhcyBEZW1vR3JlZXRpbmdXQ30gZnJvbSAnLi9kZW1vLWdyZWV0aW5nLmpzJztcblxuLy8gQ3JlYXRlcyBhIFJlYWN0IGNvbXBvbmVudCBmcm9tIGEgTGl0IGNvbXBvbmVudFxuY29uc3QgRGVtb0dyZWV0aW5nID0gY3JlYXRlQ29tcG9uZW50KHtcbiAgcmVhY3Q6IFJlYWN0LFxuICB0YWdOYW1lOiAnZGVtby1ncmVldGluZycsXG4gIGVsZW1lbnRDbGFzczogRGVtb0dyZWV0aW5nV0MsXG59KTtcblxuY29uc3Qgcm9vdCA9IGNyZWF0ZVJvb3QoZG9jdW1lbnQuZ2V0RWxlbWVudEJ5SWQoJ2FwcCcpISk7XG5cbnJvb3QucmVuZGVyKDxSb290IC8-KTtcbmZ1bmN0aW9uIFJvb3QoKSB7XG4gIGNvbnN0IFtzdGF0ZSwgc2V0U3RhdGVdID0gUmVhY3QudXNlU3RhdGUoZmFsc2UpO1xuICBSZWFjdC51c2VFZmZlY3QoKCkgPT4ge3NldEludGVydmFsKCgpID0-IHNldFN0YXRlKCh2YWx1ZSkgPT4gIXZhbHVlKSwgMTAwMCl9LCBbXSk7XG5cbiAgY29uc29sZS5sb2coXCJyZS1yZW5kZXJcIik7XG4gIHJldHVybiA8PlxuICAgPERlbW9HcmVldGluZyBuYW1lPVwiV3JhcHBlclwiIC8-XG4gICA8ZGVtby1ncmVldGluZyBuYW1lPVwiTm8gV3JhcHBlclwiIC8-XG4gIDwvPjtcbn0ifSx7Im5hbWUiOiJkZW1vLWdyZWV0aW5nLnRzIiwiY29udGVudCI6ImltcG9ydCB7aHRtbCwgY3NzLCBMaXRFbGVtZW50fSBmcm9tICdsaXQnO1xuaW1wb3J0IHtjdXN0b21FbGVtZW50LCBwcm9wZXJ0eX0gZnJvbSAnbGl0L2RlY29yYXRvcnMuanMnO1xuQGN1c3RvbUVsZW1lbnQoJ2RlbW8tZ3JlZXRpbmcnKVxuZXhwb3J0IGNsYXNzIERlbW9HcmVldGluZyBleHRlbmRzIExpdEVsZW1lbnQge1xuICBfbmFtZSA9IGBgO1xuICBAcHJvcGVydHkoKVxuICBzZXQgbmFtZShuYW1lOnN0cmluZykge1xuICAgIGNvbnNvbGUubG9nKFwic2V0dGVyIHRyaWdnZXJlZCwgb2xkIHZhbHVlOiBcIix0aGlzLl9uYW1lLCBcIi4gbmV3IHZhbHVlOiBcIiwgbmFtZSk7XG4gICAgdGhpcy5fbmFtZSA9IG5hbWU7XG4gIH1cbiAgZ2V0IG5hbWUoKTogc3RyaW5nIHtcbiAgICByZXR1cm4gdGhpcy5fbmFtZTtcbiAgfVxuXG4gIHJlbmRlcigpIHsgcmV0dXJuIHRoaXMubmFtZTsgfVxufVxuXG5kZWNsYXJlIGdsb2JhbCB7XG4gIGludGVyZmFjZSBIVE1MRWxlbWVudFRhZ05hbWVNYXAge1xuICAgICdkZW1vLWdyZWV0aW5nJzogRGVtb0dyZWV0aW5nO1xuICB9XG59XG4ifSx7Im5hbWUiOiJyZWFjdC5kLnRzIiwiY29udGVudCI6ImRlY2xhcmUgbW9kdWxlICdodHRwczovL2VzbS5zaC9yZWFjdEAxOCcge1xuICBleHBvcnQgZGVmYXVsdCBSZWFjdDtcbn1cblxuZGVjbGFyZSBtb2R1bGUgJ2h0dHBzOi8vZXNtLnNoL3JlYWN0LWRvbUAxOC9jbGllbnQnIHtcbiAgZXhwb3J0ICogZnJvbSAncmVhY3QtZG9tL2NsaWVudCc7XG59IiwiaGlkZGVuIjp0cnVlfSx7Im5hbWUiOiJpbmRleC5odG1sIiwiY29udGVudCI6Ik9wZW4gY29uc29sZS4gU2VlIHRoYXQgd3JhcHBlciB0cmlnZ2VycyBzZXR0ZXIgb24gZWFjaCByZS1yZW5kZXIuIFdpdGhvdXQgd3JhcHBlciwgc2V0dGVyIG9ubHkgdHJpZ2dlcmVkIGlmIHZhbHVlIGNoYW5nZWQuPGJyLz5cbjxzY3JpcHQgdHlwZT1cIm1vZHVsZVwiIHNyYz1cIi4vYXBwLmpzXCI-PC9zY3JpcHQ-PGRpdiBpZD1cImFwcFwiPjwvZGl2PlxuIn0seyJuYW1lIjoicGFja2FnZS5qc29uIiwiY29udGVudCI6IntcbiAgXCJkZXBlbmRlbmNpZXNcIjoge1xuICAgIFwibGl0XCI6IFwiXjNcIixcbiAgICBcIkB0eXBlcy9yZWFjdFwiOiBcIjE4LjIuN1wiLFxuICAgIFwiQHR5cGVzL3JlYWN0LWRvbVwiOiBcIl4xOFwiLFxuICAgIFwiQHR5cGVzL3JlYWN0LWRvbS9jbGllbnRcIjogXCJeMThcIlxuICB9XG59IiwiaGlkZGVuIjp0cnVlfV0
### Workaround
1. React component needs to set properties manually in a use effect (bad ux for consumer)
2. Update to react 19 and don't use the wrapper. However, some enterprise and LTS customers will be stuck on react 18 for the next 2-3 years.
3. The web components needs to diff the value inside the setter - requires a lot of boilerplate:
- Is it Lit's recommendation that every component setter must always diff manually against the old value?
- This seems redundant with the `hasChanged()` concept. And it is not trivial for the setter to get a reference to its hasChanged() function.
- **Even besides that**, the `hasChanged()` concept is not sufficient as it compares against the getter value, rather than against the value that was set by the user. Imagine the component auto-casts a string to an object value in the setter.
- Ideally the setter should not trigger again if it is triggered again with the same string value - doing so is not trivial as each setter now needs two backing properties (the backing value and the raw user-set value). That is why LIt's wrapper behavior is problematic.
Issues with current wrapper behavior: needless work on re-render. causes infinite re-loading in some components. requires a lot of boilerplate to work around in web component.
### Is this a regression?
No or unsure. This never worked, or I haven't tried before.
### Affected versions
1.0.7
### Browser/OS/Node environment
Chrome: 137.0.7151.69 (Official Build) (arm64)
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start in packages/react/src/create-component.ts around the linked line and reproduce the behavior with the provided Lit playground example. Compare wrapper updates with React 18's unchanged-value behavior; done means an unchanged prop no longer triggers the web component setter on re-render without requiring consumer workarounds.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- react, typescript
- Domain
- frontend
- Issue type
- Bug
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Activity status
- Stale
- Clarity
- Clearly specified
- Newbie friendliness
- 48/100