Skip to content

Commit 6095aa8

Browse files
authored
fix(react/runtime): fix error in suspense (#1569)
<!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Bug Fixes** * Resolved a crash (“Cannot read properties of undefined”) in certain Suspense rendering/hydration scenarios, improving stability. * Ensures correct fallback display and final content after hydration in flows without an initial hydrate. * **Chores** * Applied a patch-level dependency update and added a changeset entry for release tracking. * **Tests** * Added a test validating Suspense behavior without initial hydrate, covering fallback-to-content transition and hydration-driven updates. <!-- end of auto-generated comment: release notes by coderabbit.ai --> ## Checklist <!--- Check and mark with an "x" --> - [x] Tests updated (or not required). - [ ] Documentation updated (or not required). - [ ] Changeset added, and when a BREAKING CHANGE occurs, it needs to be clearly marked (or not required).
1 parent 59be6b2 commit 6095aa8

File tree

3 files changed

+74
-2
lines changed

3 files changed

+74
-2
lines changed

.changeset/rotten-queens-drum.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@lynx-js/react": patch
3+
---
4+
5+
fix: `Cannot read properties of undefined` error when using `Suspense`

packages/react/runtime/__test__/lynx/suspense.test.jsx

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1693,4 +1693,71 @@ describe('suspense', () => {
16931693
vi.runAllTimers();
16941694
}
16951695
});
1696+
1697+
it('should render fallback and content correctly without hydrate', async () => {
1698+
const { Suspender, suspended } = createSuspender();
1699+
1700+
function Comp() {
1701+
return (
1702+
<Suspense fallback={<text>loading</text>}>
1703+
<Suspender>
1704+
<text>foo</text>
1705+
</Suspender>
1706+
</Suspense>
1707+
);
1708+
}
1709+
1710+
// background render
1711+
{
1712+
globalEnvManager.switchToBackground();
1713+
render(<Comp />, __root);
1714+
}
1715+
1716+
// render children
1717+
{
1718+
suspended.resolve();
1719+
lynx.getNativeApp().callLepusMethod.mockClear();
1720+
await Promise.resolve().then(() => {});
1721+
}
1722+
1723+
// first screen
1724+
{
1725+
globalEnvManager.switchToMainThread();
1726+
__root.__jsx = <Comp />;
1727+
renderPage();
1728+
}
1729+
1730+
// hydrate
1731+
{
1732+
// LifecycleConstant.firstScreen
1733+
globalEnvManager.switchToBackground();
1734+
lynxCoreInject.tt.OnLifecycleEvent(...globalThis.__OnLifecycleEvent.mock.calls[0]);
1735+
expect(lynx.getNativeApp().callLepusMethod).toBeCalledTimes(1);
1736+
1737+
// rLynxChange
1738+
globalEnvManager.switchToMainThread();
1739+
globalThis.__OnLifecycleEvent.mockClear();
1740+
const rLynxChange = lynx.getNativeApp().callLepusMethod.mock.calls[0];
1741+
globalThis[rLynxChange[0]](rLynxChange[1]);
1742+
expect(globalThis.__OnLifecycleEvent).not.toBeCalled();
1743+
expect(__root.__element_root).toMatchInlineSnapshot(`
1744+
<page
1745+
cssId="default-entry-from-native:0"
1746+
>
1747+
<wrapper>
1748+
<text>
1749+
<raw-text
1750+
text="foo"
1751+
/>
1752+
</text>
1753+
</wrapper>
1754+
</page>
1755+
`);
1756+
1757+
// rLynxChange callback
1758+
globalEnvManager.switchToBackground();
1759+
rLynxChange[2]();
1760+
vi.runAllTimers();
1761+
}
1762+
});
16961763
});

packages/react/runtime/src/backgroundSnapshot.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -510,7 +510,7 @@ export function hydrate(
510510
function reconstructInstanceTree(afters: BackgroundSnapshotInstance[], parentId: number, targetId?: number): void {
511511
for (const child of afters) {
512512
const id = child.__id;
513-
__globalSnapshotPatch!.push(SnapshotOperation.CreateElement, child.type, id);
513+
__globalSnapshotPatch?.push(SnapshotOperation.CreateElement, child.type, id);
514514
const values = child.__values;
515515
if (values) {
516516
child.__values = undefined;
@@ -521,6 +521,6 @@ function reconstructInstanceTree(afters: BackgroundSnapshotInstance[], parentId:
521521
child.setAttribute(key, extraProps[key]);
522522
}
523523
reconstructInstanceTree(child.childNodes, id);
524-
__globalSnapshotPatch!.push(SnapshotOperation.InsertBefore, parentId, id, targetId);
524+
__globalSnapshotPatch?.push(SnapshotOperation.InsertBefore, parentId, id, targetId);
525525
}
526526
}

0 commit comments

Comments
 (0)