Skip to content

Commit 0c4f93c

Browse files
committed
Ensure we properly re-render bailing errored children
1 parent 13a5ce7 commit 0c4f93c

File tree

2 files changed

+65
-0
lines changed

2 files changed

+65
-0
lines changed

compat/test/browser/suspense-hydration.test.js

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1001,4 +1001,62 @@ describe('suspense hydration', () => {
10011001
});
10021002
});
10031003
});
1004+
1005+
it('Should not crash when oldVNode._children is null during shouldComponentUpdate optimization', () => {
1006+
const originalHtml = '<div>Hello</div>';
1007+
scratch.innerHTML = originalHtml;
1008+
clearLog();
1009+
1010+
class ErrorBoundary extends React.Component {
1011+
constructor(props) {
1012+
super(props);
1013+
this.state = { hasError: false };
1014+
}
1015+
1016+
static getDerivedStateFromError() {
1017+
return { hasError: true };
1018+
}
1019+
1020+
render() {
1021+
return this.props.children;
1022+
}
1023+
}
1024+
1025+
const [Lazy, resolve] = createLazy();
1026+
function App() {
1027+
return (
1028+
<Suspense>
1029+
<ErrorBoundary>
1030+
<Lazy />
1031+
</ErrorBoundary>
1032+
</Suspense>
1033+
);
1034+
}
1035+
1036+
hydrate(<App />, scratch);
1037+
rerender(); // Flush rerender queue to mimic what preact will really do
1038+
expect(scratch.innerHTML).to.equal(originalHtml);
1039+
expect(getLog()).to.deep.equal([]);
1040+
clearLog();
1041+
1042+
let i = 0;
1043+
class ThrowOrRender extends React.Component {
1044+
shouldComponentUpdate() {
1045+
return i === 0;
1046+
}
1047+
render() {
1048+
if (i === 0) {
1049+
i++;
1050+
throw new Error('Test error');
1051+
}
1052+
return <div>Hello</div>;
1053+
}
1054+
}
1055+
1056+
return resolve(ThrowOrRender).then(() => {
1057+
rerender();
1058+
expect(scratch.innerHTML).to.equal(originalHtml);
1059+
clearLog();
1060+
});
1061+
});
10041062
});

src/diff/index.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -352,10 +352,12 @@ export function diff(
352352
for (let i = excessDomChildren.length; i--; ) {
353353
removeNode(excessDomChildren[i]);
354354
}
355+
markAsForce(newVNode);
355356
}
356357
} else {
357358
newVNode._dom = oldVNode._dom;
358359
newVNode._children = oldVNode._children;
360+
if (e.then) markAsForce(newVNode);
359361
}
360362
options._catchError(e, newVNode, oldVNode);
361363
}
@@ -379,6 +381,11 @@ export function diff(
379381
return newVNode._flags & MODE_SUSPENDED ? undefined : oldDom;
380382
}
381383

384+
function markAsForce(vnode) {
385+
if (vnode && vnode._component) vnode._component._force = true;
386+
if (vnode && vnode._children) vnode._children.forEach(markAsForce);
387+
}
388+
382389
/**
383390
* @param {Array<Component>} commitQueue List of components
384391
* which have callbacks to invoke in commitRoot

0 commit comments

Comments
 (0)