You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
fix(runtime): prevent unnecessary re-renders when reflecting props (#3106)
this commit fixes a very specific issue, where reflected props may lead
to unnecessary re-renders. in order to trigger the issue, each of the
following conditions must be met:
1. the lazy load build of stencil is used
2. a prop in a stencil component is created
2.1. the prop is created with `{reflect: true}`
2.2. the member is a complex type, such as `number | any`. types such as
`number` and `number | number` will not trigger the error. using the
optional token following the member's name will not create a complex
type for the purposes of this bug (e.g. `@Prop() val?: number`)
3. the stencil component containing the prop must have a `render` fn
4. the value passed to the component must not be a string. this implies
that the component cannot be rendered directly within an html file,
as the prop will be converted to a DOMString implicitly
when the conditions above are met, the problem will manifest itself as a
warning in the console when running in developer mode:
```
The state/prop "PROP_NAME" changed during rendering. This can
potentially lead to infinite-loops and other bugs"
Element <my-component val="1" class="hydrated>...</my-component>
New value 1
Old value 1
```
what is happening under the hood is the component containing the prop
will be rendered by stencil. stencil will then attempt to reflect the
prop back to the attribute on the component's element via a call to
`setAttribute(elm, val)`. `setAttribute` implicitly converts the val
provided as the second argument to a DOMString, which is then converted
to a JavaScript string.
invoking `setAttribute` initiates `attributeChangedCallback` being called on
the component, using the aforementioned string value.
`attributeChangedCallback` leads to a series of `set*` calls internally
before using the string representation of the prop, even when a number
is passed like so:
```tsx
<my-component my-prop={1}></my-component>
```
the reason for this is that when doing change detection on prop values,
stencil performs strict equality matching on the new value against an
older known value (outside the context of `attributeChangedCallback`)
using the `===` operator.
with this change, we attempt to detect a case of accidental type
coercion to a DOMString for the same value. within
`attributeChangedCallback`, the prop may be looked up on the prototype
of the constructor of the proxy component. If the prop is found on the
prototype, is of type number, and the string received matches the
number, the change is discarded.
attempts to match the string against other primitives such as boolean,
null, and undefined have been intentionally avoided for this commit for
simplicity:
- boolean attributes are considered `true` if they are present and their
value is the emtpy string or the attribute's name
- null is traditionally sets the value to the string 'null', rather than
nullifying or removing the value
this commit fixes up small issues that are inconsequential to the
implementation of this fix, but were found during the debugging
process. they are listed below:
- removing an unused tslint:disable pragma
- fixing typos
- adding curly braces where needed
- simplifying a ternary expression
0 commit comments