@@ -123,6 +123,10 @@ export function diffChildren(
 						firstChildDom = newDom;
 					}
 
+					if (excessDomChildren != null) {
+						excessDomChildren[excessDomChildren.indexOf(newDom)] = null;
+					}
+
 					if (childVNode._lastDomChild != null) {
 						// Only Fragments or components that return Fragment like VNodes will
 						// have a non-null _lastDomChild. Continue the diff from the end of
import { diff, unmount, applyRef } from './index';
import { createVNode } from '../create-element';
import { EMPTY_OBJ, EMPTY_ARR } from '../constants';
import { removeNode } from '../util';
import { getDomSibling } from '../component';

/**
 * Diff the children of a virtual node
 * @param {import('../internal').PreactElement} parentDom The DOM element whose
 * children are being diffed
 * @param {import('../internal').VNode} newParentVNode The new virtual
 * node whose children should be diff'ed against oldParentVNode
 * @param {import('../internal').VNode} oldParentVNode The old virtual
 * node whose children should be diff'ed against newParentVNode
 * @param {object} context The current context object
 * @param {boolean} isSvg Whether or not this DOM node is an SVG node
 * @param {Array<import('../internal').PreactElement>} excessDomChildren
 * @param {Array<import('../internal').Component>} commitQueue List of components
 * which have callbacks to invoke in commitRoot
 * @param {Node | Text} oldDom The current attached DOM
 * element any new dom elements should be placed around. Likely `null` on first
 * render (except when hydrating). Can be a sibling DOM element when diffing
 * Fragments that have siblings. In most cases, it starts out as `oldChildren[0]._dom`.
 * @param {boolean} isHydrating Whether or not we are in hydration
 */
export function diffChildren(
	parentDom,
	newParentVNode,
	oldParentVNode,
	context,
	isSvg,
	excessDomChildren,
	commitQueue,
	oldDom,
	isHydrating
) {
	let i, j, oldVNode, newDom, sibDom, firstChildDom, refs;

	// This is a compression of oldParentVNode!=null && oldParentVNode != EMPTY_OBJ && oldParentVNode._children || EMPTY_ARR
	// as EMPTY_OBJ._children should be `undefined`.
	let oldChildren = (oldParentVNode && oldParentVNode._children) || EMPTY_ARR;

	let oldChildrenLength = oldChildren.length;

	// Only in very specific places should this logic be invoked (top level `render` and `diffElementNodes`).
	// I'm using `EMPTY_OBJ` to signal when `diffChildren` is invoked in these situations. I can't use `null`
	// for this purpose, because `null` is a valid value for `oldDom` which can mean to skip to this logic
	// (e.g. if mounting a new tree in which the old DOM should be ignored (usually for Fragments).
	if (oldDom == EMPTY_OBJ) {
		if (excessDomChildren != null) {
			oldDom = excessDomChildren[0];
		} else if (oldChildrenLength) {
			oldDom = getDomSibling(oldParentVNode, 0);
		} else {
			oldDom = null;
		}
	}

	i = 0;
	newParentVNode._children = toChildArray(
		newParentVNode._children,
		childVNode => {
			if (childVNode != null) {
				childVNode._parent = newParentVNode;
				childVNode._depth = newParentVNode._depth + 1;

				// Check if we find a corresponding element in oldChildren.
				// If found, delete the array item by setting to `undefined`.
				// We use `undefined`, as `null` is reserved for empty placeholders
				// (holes).
				oldVNode = oldChildren[i];

				if (
					oldVNode === null ||
					(oldVNode &&
						childVNode.key == oldVNode.key &&
						childVNode.type === oldVNode.type)
				) {
					oldChildren[i] = undefined;
				} else {
					// Either oldVNode === undefined or oldChildrenLength > 0,
					// so after this loop oldVNode == null or oldVNode is a valid value.
					for (j = 0; j < oldChildrenLength; j++) {
						oldVNode = oldChildren[j];
						// If childVNode is unkeyed, we only match similarly unkeyed nodes, otherwise we match by key.
						// We always match by type (in either case).
						if (
							oldVNode &&
							childVNode.key == oldVNode.key &&
							childVNode.type === oldVNode.type
						) {
							oldChildren[j] = undefined;
							break;
						}
						oldVNode = null;
					}
				}

				oldVNode = oldVNode || EMPTY_OBJ;

				// Morph the old element into the new one, but don't append it to the dom yet
				newDom = diff(
					parentDom,
					childVNode,
					oldVNode,
					context,
					isSvg,
					excessDomChildren,
					commitQueue,
					oldDom,
					isHydrating
				);

				if ((j = childVNode.ref) && oldVNode.ref != j) {
					if (!refs) refs = [];
					if (oldVNode.ref) refs.push(oldVNode.ref, null, childVNode);
					refs.push(j, childVNode._component || newDom, childVNode);
				}

				// Only proceed if the vnode has not been unmounted by `diff()` above.
				if (newDom != null) {
					if (firstChildDom == null) {
						firstChildDom = newDom;
					}

					if (childVNode._lastDomChild != null) {
						// Only Fragments or components that return Fragment like VNodes will
						// have a non-null _lastDomChild. Continue the diff from the end of
						// this Fragment's DOM tree.
						newDom = childVNode._lastDomChild;

						// Eagerly cleanup _lastDomChild. We don't need to persist the value because
						// it is only used by `diffChildren` to determine where to resume the diff after
						// diffing Components and Fragments.
						childVNode._lastDomChild = null;
					} else if (
						excessDomChildren == oldVNode ||
						newDom != oldDom ||
						newDom.parentNode == null
					) {
						// NOTE: excessDomChildren==oldVNode above:
						// This is a compression of excessDomChildren==null && oldVNode==null!
						// The values only have the same type when `null`.

						outer: if (oldDom == null || oldDom.parentNode !== parentDom) {
							parentDom.appendChild(newDom);
						} else {
							// `j<oldChildrenLength; j+=2` is an alternative to `j++<oldChildrenLength/2`
							for (
								sibDom = oldDom, j = 0;
								(sibDom = sibDom.nextSibling) && j < oldChildrenLength;
								j += 2
							) {
								if (sibDom == newDom) {
									break outer;
								}
							}
							parentDom.insertBefore(newDom, oldDom);
						}

						// Browsers will infer an option's `value` from `textContent` when
						// no value is present. This essentially bypasses our code to set it
						// later in `diff()`. It works fine in all browsers except for IE11
						// where it breaks setting `select.value`. There it will be always set
						// to an empty string. Re-applying an options value will fix that, so
						// there are probably some internal data structures that aren't
						// updated properly.
						//
						// To fix it we make sure to reset the inferred value, so that our own
						// value check in `diff()` won't be skipped.
						if (newParentVNode.type == 'option') {
							parentDom.value = '';
						}
					}

					oldDom = newDom.nextSibling;

					if (typeof newParentVNode.type == 'function') {
						// At this point, if childVNode._lastDomChild existed, then
						// newDom = childVNode._lastDomChild per line 101. Else it is
						// the same as childVNode._dom, meaning this component returned
						// only a single DOM node
						newParentVNode._lastDomChild = newDom;
					}
				}
			}

			i++;
			return childVNode;
		}
	);

	newParentVNode._dom = firstChildDom;

	// Remove children that are not part of any vnode.
	if (excessDomChildren != null && typeof newParentVNode.type !== 'function') {
		for (i = excessDomChildren.length; i--; ) {
			if (excessDomChildren[i] != null) removeNode(excessDomChildren[i]);
		}
	}

	// Remove remaining oldChildren if there are any.
	for (i = oldChildrenLength; i--; ) {
		if (oldChildren[i] != null) unmount(oldChildren[i], oldChildren[i]);
	}

	// Set refs only after unmount
	if (refs) {
		for (i = 0; i < refs.length; i++) {
			applyRef(refs[i], refs[++i], refs[++i]);
		}
	}
}

/**
 * Flatten and loop through the children of a virtual node
 * @param {import('../index').ComponentChildren} children The unflattened
 * children of a virtual node
 * @param {(vnode: import('../internal').VNode) => import('../internal').VNode} [callback]
 * A function to invoke for each child before it is added to the flattened list.
 * @param {Array<import('../internal').VNode | string | number>} [flattened] An flat array of children to modify
 * @returns {import('../internal').VNode[]}
 */
export function toChildArray(children, callback, flattened) {
	if (flattened == null) flattened = [];

	if (children == null || typeof children === 'boolean') {
		if (callback) flattened.push(callback(null));
	} else if (Array.isArray(children)) {
		for (let i = 0; i < children.length; i++) {
			toChildArray(children[i], callback, flattened);
		}
	} else if (!callback) {
		flattened.push(children);
	} else if (typeof children === 'string' || typeof children === 'number') {
		flattened.push(callback(createVNode(null, children, null, null)));
	} else if (children._dom != null || children._component != null) {
		flattened.push(
			callback(createVNode(children.type, children.props, children.key, null))
		);
	} else {
		flattened.push(callback(children));
	}

	return flattened;
}

Just merged #2274 which has a similar fix. Are they the same?