diff --git a/compat/test/browser/suspense-hydration.test.js b/compat/test/browser/suspense-hydration.test.js
index 3526b96918..71fab2eb2c 100644
--- a/compat/test/browser/suspense-hydration.test.js
+++ b/compat/test/browser/suspense-hydration.test.js
@@ -97,6 +97,64 @@ describe('suspense hydration', () => {
});
});
+ it('Should not crash when oldVNode._children is null during shouldComponentUpdate optimization', () => {
+ const originalHtml = '
Hello
';
+ scratch.innerHTML = originalHtml;
+ clearLog();
+
+ class ErrorBoundary extends React.Component {
+ constructor(props) {
+ super(props);
+ this.state = { hasError: false };
+ }
+
+ static getDerivedStateFromError() {
+ return { hasError: true };
+ }
+
+ render() {
+ return this.props.children;
+ }
+ }
+
+ const [Lazy, resolve] = createLazy();
+ function App() {
+ return (
+
+
+
+
+
+ );
+ }
+
+ hydrate(, scratch);
+ rerender(); // Flush rerender queue to mimic what preact will really do
+ expect(scratch.innerHTML).to.equal(originalHtml);
+ expect(getLog()).to.deep.equal([]);
+ clearLog();
+
+ let i = 0;
+ class ThrowOrRender extends React.Component {
+ shouldComponentUpdate() {
+ return i === 0;
+ }
+ render() {
+ if (i === 0) {
+ i++;
+ throw new Error('Test error');
+ }
+ return Hello
;
+ }
+ }
+
+ return resolve(ThrowOrRender).then(() => {
+ rerender();
+ expect(scratch.innerHTML).to.equal(originalHtml);
+ clearLog();
+ });
+ });
+
it('should leave DOM untouched when suspending while hydrating', () => {
scratch.innerHTML = 'Hello
';
clearLog();
diff --git a/src/diff/index.js b/src/diff/index.js
index 5603586ae6..ae75d62dc9 100644
--- a/src/diff/index.js
+++ b/src/diff/index.js
@@ -309,10 +309,12 @@ export function diff(
for (let i = excessDomChildren.length; i--; ) {
removeNode(excessDomChildren[i]);
}
+ markAsForce(newVNode);
}
} else {
newVNode._dom = oldVNode._dom;
newVNode._children = oldVNode._children;
+ if (!e.then) markAsForce(newVNode);
}
options._catchError(e, newVNode, oldVNode);
}
@@ -341,6 +343,11 @@ export function diff(
return newVNode._flags & MODE_SUSPENDED ? undefined : oldDom;
}
+function markAsForce(vnode) {
+ if (vnode && vnode._component) vnode._component._force = true;
+ if (vnode && vnode._children) vnode._children.forEach(markAsForce);
+}
+
/**
* @param {Array} commitQueue List of components
* which have callbacks to invoke in commitRoot