-
Notifications
You must be signed in to change notification settings - Fork 794
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
bug: ref callback order #3564
Comments
Hi @George-Payne thanks for filing this issue! I believe you are right that this is related to #3253, what you are describing is basically the same issue, with the caveat that the behavior described in #3253 is what happens if you don't set the At present what Stencil does with Is mutating these keys something you need to do for some reason? I am wondering what the use-case is, just because my feeling at present is that the intended way to use Anyhow - just trying to understand better what you're trying to do and what the exact issue is. Thanks again for filing the issue! |
Hi Alice,
Passing a different key forces a recreation of an element, so you can start with a clean slate. I was mostly using it as an easy way to reproduce the issue. Here's a reproduction that doesn't use keys, which, though still contrived, is maybe more like something someone might write: my-component.tsximport { Host, Component, h, Listen, State } from '@stencil/core';
@Component({
tag: 'my-component',
styleUrl: 'my-component.css',
shadow: true,
})
export class MyComponent {
@State() wrap: boolean = false;
componentDidLoad() {
setInterval(() => {
this.wrap = !this.wrap;
}, 3_000);
}
@Listen('click')
handleClick() {
console.log(this.innerDiv);
}
renderInner() {
return <div ref={this.captureDiv}>{'hello'}</div>;
}
render() {
return (
<Host>
{this.wrap ? (
<article>
<h1>{'article'}</h1>
{this.renderInner()}
</article>
) : (
this.renderInner()
)}
</Host>
);
}
private innerDiv: HTMLDivElement | null;
private captureDiv = (r: HTMLDivElement | null) => {
console.log(this.wrap, r);
this.innerDiv = r;
};
} The console gives:
As the refs are cleaned up after the new ref is called, after the first wrap (I'm not expecting the div inner div to be reused or anything, as the render tree has changed. Just that refs are unrefed before the new refs come in.) Again, for reference, here is a react equivalent, showing the expected behaviour https://jsfiddle.net/3a47xzjd/5/
|
Ok that makes sense. The issue reproducing without setting a key is basically what #3253 is all about. My current take on the situation is that Stencil's VDom implementation is, at present, not correct because you shouldn't need to set keys in order to get the correct behavior for setting a Two points:
Anyhow, that is my understanding of where this is at presently. I believe in practice Stencil users can avoid the problem of inappropriately-null A difficulty that the Stencil team is dealing with is that the virtual DOM code is fairly complex and none of us are the original authors of it, so it is somewhat time consuming for us to understand and change it. Still, we are game to try! As part of addressing #3253 we plan to 1) document the existing behavior and the use-case for setting In the longer term we may look at rewriting the VDom code to provide better guarantees and produce correct behavior in this case without having to use Footnotes
|
@George-Payne just had a bit more of a think on this and I'm going to go ahead and label it to ingest into our backlog, I'm going to keep #3253 open for now for tracking some more work related specifically to the |
Thanks for extensive reply, and the docs you linked were interesting.
In the second example, the issue can't be resolved by adding a key, due to the changing the nodes position in the tree. my-component.tsx (with key)import { Host, Component, h, Listen, State } from '@stencil/core';
@Component({
tag: 'my-component',
styleUrl: 'my-component.css',
shadow: true,
})
export class MyComponent {
@State() wrap: boolean = false;
componentDidLoad() {
setInterval(() => {
this.wrap = !this.wrap;
}, 3_000);
}
@Listen('click')
handleClick() {
console.log(this.innerDiv);
}
renderInner() {
return (
<div ref={this.captureDiv} key={'static-key'}>
{'hello'}
</div>
);
}
render() {
return (
<Host>
{this.wrap ? (
<article>
<h1>{'article'}</h1>
{this.renderInner()}
</article>
) : (
this.renderInner()
)}
</Host>
);
}
private innerDiv: HTMLDivElement | null;
private captureDiv = (r: HTMLDivElement | null) => {
console.log(this.wrap, r);
this.innerDiv = r;
};
}
my-component with key console output.
Because the tree goes from:
to:
and The call order then looks something like this:
So as
(In my even more limited understanding) react is doing the same thing (just comparing children). but because (the equivalent of) |
Coludn't find an equivalent issue in
This seems to be the same thing in 'preact': With the fix: But this doesn't seem to map directly to anything in stencil, as far as I can see. |
That all makes sense to me, and you're right that currently there's no real way to workaround this issue if nodes are moving up and down the tree like that (the bit of the I think we could easily implement something along the lines of the fix in that Preact PR. Thanks for finding and sharing all these things! My feeling at present is that this issue is probably present in a fair few virtual DOM implementations and fixed only in some (like Preact, React, etc). |
Prerequisites
Stencil Version
2.17.4
Current Behavior
When using a ref callback, new refs are given before old refs are nullified.
For example, if you change a key on an element, the ref will be called with the new
div
before it is called withnull
my-component.tsx
Will give:
This is potentially problematic, as if you track a single ref:
The reference will be set to null after the new ref is given.
Expected Behavior
Old refs should be nullified before new refs are given.
For reference, equivalent code in react will give null before the next ref is given:
https://jsfiddle.net/yk23w1dj/16/
Steps to Reproduce
https://github.com/George-Payne/stencil-bug-reproductions/tree/ref-key-order
npm install
npm run dev
Open in browser and view console:
npm run prod
Code Reproduction URL
https://github.com/George-Payne/stencil-bug-reproductions/tree/ref-key-order
Additional Information
Possibly related to: #3253
The text was updated successfully, but these errors were encountered: