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

remove _nextDom resetting as it collides with nested fragment switching #3738

Merged
merged 1 commit into from Sep 29, 2022

Conversation

JoviDeCroock
Copy link
Member

@JoviDeCroock JoviDeCroock commented Sep 19, 2022

fixes #3737

I have tracked this down for a bit and it looks like when the _nextDom is set to <div /> from the second render we switch it to become <p>first paragraph which means instead of appending the <p>second paragraph we will be adding it as the first element. If instead of i + 1 in getDomSibling we say i it also works due to us removing the null element first

@github-actions
Copy link

github-actions bot commented Sep 19, 2022

📊 Tachometer Benchmark Results

Summary

duration

  • 02_replace1k: unsure 🔍 -13% - +2% (-13.40ms - +1.99ms)
    preact-local vs preact-master
  • 03_update10th1k_x16: unsure 🔍 -4% - +3% (-1.79ms - +1.33ms)
    preact-local vs preact-master
  • 07_create10k: unsure 🔍 -2% - +2% (-27.69ms - +30.23ms)
    preact-local vs preact-master
  • filter_list: faster ✔ 2% - 7% (0.67ms - 2.21ms)
    preact-local vs preact-master
  • hydrate1k: unsure 🔍 -2% - +1% (-2.39ms - +1.27ms)
    preact-local vs preact-master
  • many_updates: unsure 🔍 -4% - +3% (-0.99ms - +0.73ms)
    preact-local vs preact-master
  • text_update: unsure 🔍 -2% - +6% (-0.07ms - +0.21ms)
    preact-local vs preact-master
  • todo: unsure 🔍 -1% - +1% (-0.27ms - +0.61ms)
    preact-local vs preact-master

usedJSHeapSize

  • 02_replace1k: unsure 🔍 -2% - +11% (-0.06ms - +0.41ms)
    preact-local vs preact-master
  • 03_update10th1k_x16: unsure 🔍 -0% - +1% (-0.02ms - +0.03ms)
    preact-local vs preact-master
  • 07_create10k: slower ❌ 1% - 1% (0.16ms - 0.16ms)
    preact-local vs preact-master
  • filter_list: slower ❌ 1% - 1% (0.02ms - 0.03ms)
    preact-local vs preact-master
  • hydrate1k: unsure 🔍 -1% - +3% (-0.06ms - +0.17ms)
    preact-local vs preact-master
  • many_updates: unsure 🔍 +0% - +0% (+0.00ms - +0.00ms)
    preact-local vs preact-master
  • text_update: unsure 🔍 +0% - +0% (+0.00ms - +0.00ms)
    preact-local vs preact-master
  • todo: unsure 🔍 -2% - +2% (-0.02ms - +0.03ms)
    preact-local vs preact-master

Results

02_replace1k

duration

VersionAvg timevs preact-mastervs preact-localvs preact-hooks
preact-master100.78ms - 110.22ms-unsure 🔍
-2% - +14%
-1.99ms - +13.40ms
faster ✔
0% - 10%
0.16ms - 11.65ms
preact-local93.72ms - 105.87msunsure 🔍
-13% - +2%
-13.40ms - +1.99ms
-faster ✔
4% - 16%
4.72ms - 18.51ms
preact-hooks108.14ms - 114.67msunsure 🔍
-0% - +11%
+0.16ms - +11.65ms
slower ❌
4% - 19%
4.72ms - 18.51ms
-

usedJSHeapSize

VersionAvg timevs preact-mastervs preact-localvs preact-hooks
preact-master3.75ms - 4.03ms-unsure 🔍
-10% - +1%
-0.41ms - +0.06ms
faster ✔
3% - 11%
0.13ms - 0.47ms
preact-local3.88ms - 4.25msunsure 🔍
-2% - +11%
-0.06ms - +0.41ms
-unsure 🔍
-8% - +2%
-0.33ms - +0.08ms
preact-hooks4.10ms - 4.29msslower ❌
3% - 12%
0.13ms - 0.47ms
unsure 🔍
-2% - +8%
-0.08ms - +0.33ms
-

run-warmup-0

VersionAvg timevs preact-mastervs preact-localvs preact-hooks
preact-master0.96ms - 0.99ms-unsure 🔍
-8% - +6%
-0.08ms - +0.06ms
unsure 🔍
-3% - +3%
-0.03ms - +0.03ms
preact-local0.92ms - 1.05msunsure 🔍
-6% - +8%
-0.06ms - +0.08ms
-unsure 🔍
-6% - +8%
-0.06ms - +0.08ms
preact-hooks0.95ms - 1.00msunsure 🔍
-3% - +3%
-0.03ms - +0.03ms
unsure 🔍
-8% - +6%
-0.08ms - +0.06ms
-

run-warmup-1

VersionAvg timevs preact-mastervs preact-localvs preact-hooks
preact-master0.59ms - 0.79ms-unsure 🔍
-11% - +37%
-0.06ms - +0.22ms
slower ❌
21% - 70%
0.11ms - 0.33ms
preact-local0.52ms - 0.71msunsure 🔍
-30% - +8%
-0.22ms - +0.06ms
-slower ❌
6% - 51%
0.04ms - 0.24ms
preact-hooks0.44ms - 0.51msfaster ✔
20% - 43%
0.11ms - 0.33ms
faster ✔
9% - 36%
0.04ms - 0.24ms
-

run-warmup-2

VersionAvg timevs preact-mastervs preact-localvs preact-hooks
preact-master0.59ms - 0.73ms-unsure 🔍
-6% - +24%
-0.03ms - +0.14ms
slower ❌
5% - 41%
0.03ms - 0.21ms
preact-local0.55ms - 0.66msunsure 🔍
-21% - +5%
-0.14ms - +0.03ms
-unsure 🔍
-3% - +29%
-0.01ms - +0.15ms
preact-hooks0.48ms - 0.60msfaster ✔
6% - 31%
0.03ms - 0.21ms
unsure 🔍
-24% - +1%
-0.15ms - +0.01ms
-

run-warmup-3

VersionAvg timevs preact-mastervs preact-localvs preact-hooks
preact-master0.33ms - 0.39ms-unsure 🔍
-32% - +13%
-0.14ms - +0.06ms
faster ✔
9% - 28%
0.03ms - 0.13ms
preact-local0.31ms - 0.49msunsure 🔍
-17% - +38%
-0.06ms - +0.14ms
-unsure 🔍
-32% - +13%
-0.14ms - +0.06ms
preact-hooks0.41ms - 0.47msslower ❌
8% - 36%
0.03ms - 0.13ms
unsure 🔍
-17% - +38%
-0.06ms - +0.14ms
-

run-warmup-4

VersionAvg timevs preact-mastervs preact-localvs preact-hooks
preact-master1.47ms - 4.32ms-unsure 🔍
-52% - +80%
-1.26ms - +1.98ms
slower ❌
118% - 689%
0.89ms - 3.75ms
preact-local1.76ms - 3.31msunsure 🔍
-63% - +38%
-1.98ms - +1.26ms
-slower ❌
157% - 525%
1.17ms - 2.76ms
preact-hooks0.41ms - 0.74msfaster ✔
69% - 91%
0.89ms - 3.75ms
faster ✔
68% - 87%
1.17ms - 2.76ms
-

run-final

VersionAvg timevs preact-mastervs preact-localvs preact-hooks
preact-master0.26ms - 0.31ms-unsure 🔍
-23% - +5%
-0.07ms - +0.02ms
unsure 🔍
-5% - +27%
-0.01ms - +0.07ms
preact-local0.27ms - 0.35msunsure 🔍
-7% - +27%
-0.02ms - +0.07ms
-slower ❌
2% - 42%
0.01ms - 0.10ms
preact-hooks0.23ms - 0.28msunsure 🔍
-23% - +3%
-0.07ms - +0.01ms
faster ✔
4% - 31%
0.01ms - 0.10ms
-
03_update10th1k_x16

duration

VersionAvg timevs preact-mastervs preact-localvs preact-hooks
preact-master38.78ms - 41.20ms-unsure 🔍
-3% - +5%
-1.33ms - +1.79ms
unsure 🔍
-3% - +4%
-1.28ms - +1.72ms
preact-local38.77ms - 40.75msunsure 🔍
-4% - +3%
-1.79ms - +1.33ms
-unsure 🔍
-3% - +3%
-1.34ms - +1.32ms
preact-hooks38.88ms - 40.66msunsure 🔍
-4% - +3%
-1.72ms - +1.28ms
unsure 🔍
-3% - +3%
-1.32ms - +1.34ms
-

usedJSHeapSize

VersionAvg timevs preact-mastervs preact-localvs preact-hooks
preact-master3.72ms - 3.75ms-unsure 🔍
-1% - +0%
-0.03ms - +0.02ms
faster ✔
8% - 9%
0.34ms - 0.39ms
preact-local3.73ms - 3.76msunsure 🔍
-0% - +1%
-0.02ms - +0.03ms
-faster ✔
8% - 9%
0.33ms - 0.39ms
preact-hooks4.08ms - 4.12msslower ❌
9% - 10%
0.34ms - 0.39ms
slower ❌
9% - 10%
0.33ms - 0.39ms
-
07_create10k

duration

VersionAvg timevs preact-mastervs preact-localvs preact-hooks
preact-master1788.54ms - 1828.73ms-unsure 🔍
-2% - +2%
-30.23ms - +27.69ms
faster ✔
1% - 4%
23.24ms - 75.56ms
preact-local1789.05ms - 1830.75msunsure 🔍
-2% - +2%
-27.69ms - +30.23ms
-faster ✔
1% - 4%
21.39ms - 74.87ms
preact-hooks1841.29ms - 1874.77msslower ❌
1% - 4%
23.24ms - 75.56ms
slower ❌
1% - 4%
21.39ms - 74.87ms
-

usedJSHeapSize

VersionAvg timevs preact-mastervs preact-localvs preact-hooks
preact-master25.52ms - 25.52ms-faster ✔
1% - 1%
0.16ms - 0.16ms
faster ✔
10% - 10%
2.71ms - 2.71ms
preact-local25.68ms - 25.68msslower ❌
1% - 1%
0.16ms - 0.16ms
-faster ✔
9% - 9%
2.55ms - 2.55ms
preact-hooks28.23ms - 28.23msslower ❌
11% - 11%
2.71ms - 2.71ms
slower ❌
10% - 10%
2.55ms - 2.55ms
-
filter_list

duration

VersionAvg timevs preact-mastervs preact-localvs preact-hooks
preact-master30.16ms - 31.34ms-slower ❌
2% - 8%
0.67ms - 2.21ms
unsure 🔍
-2% - +3%
-0.65ms - +1.02ms
preact-local28.82ms - 29.80msfaster ✔
2% - 7%
0.67ms - 2.21ms
-faster ✔
2% - 7%
0.48ms - 2.03ms
preact-hooks29.97ms - 31.16msunsure 🔍
-3% - +2%
-1.02ms - +0.65ms
slower ❌
2% - 7%
0.48ms - 2.03ms
-

usedJSHeapSize

VersionAvg timevs preact-mastervs preact-localvs preact-hooks
preact-master1.75ms - 1.75ms-faster ✔
1% - 1%
0.02ms - 0.03ms
faster ✔
14% - 15%
0.28ms - 0.31ms
preact-local1.78ms - 1.78msslower ❌
1% - 1%
0.02ms - 0.03ms
-faster ✔
13% - 14%
0.26ms - 0.29ms
preact-hooks2.04ms - 2.07msslower ❌
16% - 18%
0.28ms - 0.31ms
slower ❌
15% - 16%
0.26ms - 0.29ms
-
hydrate1k

duration

VersionAvg timevs preact-mastervs preact-localvs preact-hooks
preact-master130.67ms - 133.08ms-unsure 🔍
-1% - +2%
-1.27ms - +2.39ms
unsure 🔍
-3% - +0%
-3.86ms - +0.51ms
preact-local129.94ms - 132.69msunsure 🔍
-2% - +1%
-2.39ms - +1.27ms
-unsure 🔍
-3% - +0%
-4.52ms - +0.04ms
preact-hooks131.73ms - 135.38msunsure 🔍
-0% - +3%
-0.51ms - +3.86ms
unsure 🔍
-0% - +3%
-0.04ms - +4.52ms
-

usedJSHeapSize

VersionAvg timevs preact-mastervs preact-localvs preact-hooks
preact-master6.27ms - 6.44ms-unsure 🔍
-3% - +1%
-0.17ms - +0.06ms
faster ✔
6% - 11%
0.43ms - 0.78ms
preact-local6.33ms - 6.50msunsure 🔍
-1% - +3%
-0.06ms - +0.17ms
-faster ✔
5% - 10%
0.37ms - 0.72ms
preact-hooks6.80ms - 7.11msslower ❌
7% - 12%
0.43ms - 0.78ms
slower ❌
6% - 11%
0.37ms - 0.72ms
-
many_updates

duration

VersionAvg timevs preact-mastervs preact-localvs preact-hooks
preact-master22.67ms - 23.92ms-unsure 🔍
-3% - +4%
-0.73ms - +0.99ms
faster ✔
6% - 12%
1.55ms - 3.10ms
preact-local22.57ms - 23.76msunsure 🔍
-4% - +3%
-0.99ms - +0.73ms
-faster ✔
7% - 12%
1.70ms - 3.21ms
preact-hooks25.16ms - 26.08msslower ❌
6% - 14%
1.55ms - 3.10ms
slower ❌
7% - 14%
1.70ms - 3.21ms
-

usedJSHeapSize

VersionAvg timevs preact-mastervs preact-localvs preact-hooks
preact-master4.82ms - 4.82ms-unsure 🔍
-0% - -0%
-0.00ms - -0.00ms
faster ✔
8% - 8%
0.45ms - 0.45ms
preact-local4.82ms - 4.82msunsure 🔍
+0% - +0%
+0.00ms - +0.00ms
-faster ✔
8% - 8%
0.45ms - 0.45ms
preact-hooks5.26ms - 5.26msslower ❌
9% - 9%
0.45ms - 0.45ms
slower ❌
9% - 9%
0.45ms - 0.45ms
-
text_update

duration

VersionAvg timevs preact-mastervs preact-localvs preact-hooks
preact-master3.66ms - 3.80ms-unsure 🔍
-6% - +2%
-0.21ms - +0.07ms
faster ✔
5% - 9%
0.18ms - 0.35ms
preact-local3.68ms - 3.92msunsure 🔍
-2% - +6%
-0.07ms - +0.21ms
-faster ✔
2% - 8%
0.06ms - 0.32ms
preact-hooks3.94ms - 4.04msslower ❌
5% - 9%
0.18ms - 0.35ms
slower ❌
1% - 9%
0.06ms - 0.32ms
-

usedJSHeapSize

VersionAvg timevs preact-mastervs preact-localvs preact-hooks
preact-master0.98ms - 0.98ms-unsure 🔍
-0% - -0%
-0.00ms - -0.00ms
faster ✔
2% - 2%
0.02ms - 0.02ms
preact-local0.98ms - 0.98msunsure 🔍
+0% - +0%
+0.00ms - +0.00ms
-faster ✔
1% - 1%
0.01ms - 0.01ms
preact-hooks1.00ms - 1.00msslower ❌
2% - 2%
0.02ms - 0.02ms
slower ❌
2% - 2%
0.01ms - 0.01ms
-
todo

duration

VersionAvg timevs preact-mastervs preact-localvs preact-hooks
preact-master49.54ms - 49.88ms-unsure 🔍
-1% - +1%
-0.61ms - +0.27ms
faster ✔
6% - 7%
3.03ms - 3.50ms
preact-local49.48ms - 50.29msunsure 🔍
-1% - +1%
-0.27ms - +0.61ms
-faster ✔
5% - 7%
2.66ms - 3.53ms
preact-hooks52.82ms - 53.14msslower ❌
6% - 7%
3.03ms - 3.50ms
slower ❌
5% - 7%
2.66ms - 3.53ms
-

usedJSHeapSize

VersionAvg timevs preact-mastervs preact-localvs preact-hooks
preact-master1.34ms - 1.37ms-unsure 🔍
-2% - +2%
-0.03ms - +0.02ms
unsure 🔍
-1% - +3%
-0.02ms - +0.03ms
preact-local1.34ms - 1.37msunsure 🔍
-2% - +2%
-0.02ms - +0.03ms
-unsure 🔍
-1% - +3%
-0.02ms - +0.04ms
preact-hooks1.33ms - 1.37msunsure 🔍
-3% - +1%
-0.03ms - +0.02ms
unsure 🔍
-3% - +1%
-0.04ms - +0.02ms
-

tachometer-reporter-action v2 for Benchmarks

@coveralls
Copy link

coveralls commented Sep 19, 2022

Coverage Status

Coverage remained the same at 99.527% when pulling 0a6e326 on fragments into dfd45aa on master.

@github-actions
Copy link

github-actions bot commented Sep 19, 2022

Size Change: -149 B (0%)

Total Size: 52.9 kB

Filename Size Change
dist/preact.js 4.04 kB -20 B (0%)
dist/preact.min.js 4.06 kB -26 B (0%)
dist/preact.min.module.js 4.05 kB -26 B (0%)
dist/preact.min.umd.js 4.08 kB -25 B (0%)
dist/preact.module.js 4.05 kB -27 B (0%)
dist/preact.umd.js 4.1 kB -25 B (0%)
ℹ️ View Unchanged
Filename Size Change
compat/dist/compat.js 3.78 kB 0 B
compat/dist/compat.module.js 3.72 kB 0 B
compat/dist/compat.umd.js 3.85 kB 0 B
debug/dist/debug.js 3.01 kB 0 B
debug/dist/debug.module.js 3.01 kB 0 B
debug/dist/debug.umd.js 3.09 kB 0 B
devtools/dist/devtools.js 231 B 0 B
devtools/dist/devtools.module.js 239 B 0 B
devtools/dist/devtools.umd.js 314 B 0 B
hooks/dist/hooks.js 1.56 kB 0 B
hooks/dist/hooks.module.js 1.59 kB 0 B
hooks/dist/hooks.umd.js 1.64 kB 0 B
jsx-runtime/dist/jsxRuntime.js 358 B 0 B
jsx-runtime/dist/jsxRuntime.module.js 324 B 0 B
jsx-runtime/dist/jsxRuntime.umd.js 439 B 0 B
test-utils/dist/testUtils.js 442 B 0 B
test-utils/dist/testUtils.module.js 444 B 0 B
test-utils/dist/testUtils.umd.js 526 B 0 B

compressed-size-action

@@ -237,7 +237,9 @@ describe('Fragment', () => {
expectDomLogToBe([
'<div>.appendChild(#text)',
'<div>122.insertBefore(<div>1, <span>1)',
'<span>1.remove()'
'<span>1.remove()',
'<div>122.appendChild(<span>2)',
Copy link
Member

@developit developit Sep 19, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These seem incorrect. We're re-appending the spans even though they're already in the correct position.

The extra insertions is likely what is causing the slowdown in 03_update10th1k_x16.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yea, this was a performance optimization but it breaks unexpectedly. I can look into building some offset based calculator but generally this optimization is impossible to get right because of several reasons

  • we can have nullish children
  • we can have empty fragments as children

both these conditions increment the i counter meaning we will start looking for a dom-sibling in an erroneous position, hence the optimization fails in the issue-case and multiple others 😅 also this does not account for moved nodes

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also do read that the DOM is still correct we just can't prevent these dupe insertions when moving

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agree, that this seems to point at a shortcoming of the current reconciler algo.

@JoviDeCroock JoviDeCroock merged commit 284a8b0 into master Sep 29, 2022
@JoviDeCroock JoviDeCroock deleted the fragments branch September 29, 2022 08:42
JoviDeCroock added a commit that referenced this pull request Oct 26, 2022
andrewiggins added a commit that referenced this pull request Feb 2, 2023
…#3875)

## Why does #3814 fail on master?

When transitioning from the tree

```jsx
<div>
  <Fragment>
    <span>0</span>
    <span>1</span>
  </Fragment>
  <input />
</div>
```

to the tree (note `<span>1</span>` was unmounted)

```jsx
<div>
  <Fragment>
    <span>0</span>
  </Fragment>
  <input />
</div>
```

the `master` branch has a bug where it will re-append all the children after the `Fragment`. This re-appending of the `<input>` causes it to lose focus. The reason for this re-appending is that when exiting `diffChildren` for the Fragment, it's `_nextDom` pointer is set to `<span>1</span>`. Since `<span>1</span>` is unmounted it's `parentNode` is `null`. When diffing the `input` element, we hit the `oldDom.parentNode !== parentDom` condition in `placeChild` which re-appends the `<input>` tag and sets oldDom to `null` causing all siblings of `<input>` to re-append.

When diffing components/Fragments, the `_nextDom` pointer should point to where in the DOM tree the diff should continue. So when unmounting `<span>1</span>`, the Fragment's `_nextDom` should point to the `<input>` tag. The previous code in `diffChildren` removed in #3738 was intended to fix this by detecting when `_nextDom` pointed to a node that we were unmounting and reset it to it's sibling. However, that code (copied below) had a correctness bug prompting its removal (#3737). Let's look at this bug.

```js
// Remove remaining oldChildren if there are any.
for (i = oldChildrenLength; i--; ) {
	if (oldChildren[i] != null) {
		if (
			typeof newParentVNode.type == 'function' &&
			oldChildren[i]._dom != null &&
			oldChildren[i]._dom == newParentVNode._nextDom
		) {
			// If the newParentVNode.__nextDom points to a dom node that is about to
			// be unmounted, then get the next sibling of that vnode and set
			// _nextDom to it
			newParentVNode._nextDom = getDomSibling(oldParentVNode, i + 1);
		}

		unmount(oldChildren[i], oldChildren[i]);
	}
}
```

## What caused #3737?

Here is a simplified repro of the issue from #3737:

```jsx
function App() {
	const [condition] = useState(true);
	return this.state.condition ? (
		// Initial render
		<>
			<div>1</div>
			<Fragment>
				<div>A</div>
				<div>B</div>
			</Fragment>
			<div>2</div>
		</>
	) : (
		// Second render: unmount <div>B and move Fragment up
		<>
			<Fragment>
				<div>A</div>
			</Fragment>
			<div>1</div>
			<div>2</div>
		</>
	);
}
```

The first render creates the DOM `1 A B 2` (each wrapped in divs) and when changing the `condition` state, it should rerender to produce `A 1 2` but instead we get `1 A 2`. Why?

When rerendering `App` we unmount `<div>B</div>`, which is a child of a Fragment. This unmounting triggers the call to `getDomSibling` in the code above. `getDomSibling` has a line of code in it that looks like `vnode._parent._children.indexOf(vnode)`. This line of code doesn't work in this situation because when rerendering a component, we only make a shallow copy of the old VNode tree. So when a child of `oldParentVNode` (the `App` component in this case) tries to access it's parent through the `_parent` pointer (e.g. the `Fragment` parent of `<div>A</div>`), it's `_parent` pointer points to the new VNode tree which we are in progress of diffing and modifying. Ultimately, this tree mismatch (trying to access the old VNode tree but getting the in-progress new tree) causes us to get the wrong DOM pointer and leads to incorrect DOM ordering.

In summary, when unmounting `<div>B</div>`, we need `getDomSibling` to return `<div>2</div>` since that is the next DOM sibling in the old VNode tree. Instead, because our VNode pointers are mixed at this stage of diffing, `getDomSibling` doesn't work correctly and we get back the wrong DOM element.

## Why didn't other tests catch this?

Other tests only do top-level render calls (e.g. `render(<App condition={true} />)` then `render(<App condition={false} />)`) which generate brand-new VNode trees with no shared pointers. They did not test renders originating from `setState` calls which go through a different code path and reuse VNode trees which share pointers across the old and new trees.

## The fix

The initial fix for this is to replace `getDomSibling` with a call to `dom.nextSibling` to get the actual next DOM sibling. (I found a situation in which this doesn't work optimally. I'll open a separate PR for that.)

## Final thoughts

One additional thought I have here is that walking through this has given me more confidence in our approach for v11. First, we do unmounts before insertions so we don't have to do this additional DOM pointer checking. Also, by diffing backwards, we ensure that our `_next` pointers are correct when we go to search what DOM element to insert an element before.


Fixes #3814, #3737
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Preact adding elements out of order when replacing elements in DOM
4 participants