Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/early-crabs-lie.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@lynx-js/react": patch
---

Fixed blank screen issues with nested lists. Lazily created nested lists were being flushed but not properly recorded, causing rendering failures.
219 changes: 218 additions & 1 deletion packages/react/runtime/__test__/list.test.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -4045,6 +4045,223 @@ describe('nested list', () => {
`);
});

it('should record lazily created nested lists', () => {
const s1 = __SNAPSHOT__(
<view>
<text>s1</text>
<list>{HOLE}</list>
</view>,
);
const s2 = __SNAPSHOT__(
<list-item>
<text>s2</text>
<list>{HOLE}</list>
</list-item>,
);
const s3 = __SNAPSHOT__(
<list-item>
<text>s3</text>
</list-item>,
);

const a = new SnapshotInstance(s);

const b = new SnapshotInstance(s1);
a.insertBefore(b);
b.ensureElements();
const parentListRef = b.__elements[3];

const c1 = new SnapshotInstance(s2);
const c2 = new SnapshotInstance(s2);
const c3 = new SnapshotInstance(s2);

b.insertBefore(c1);
b.insertBefore(c2);
b.insertBefore(c3);

const d1 = new SnapshotInstance(s3);
c1.insertBefore(d1);

const d2 = new SnapshotInstance(s3);
c2.insertBefore(d2);

const d3 = new SnapshotInstance(s3);
c3.insertBefore(d3);
__pendingListUpdates.flush();

expect(__pendingListUpdates.values).toMatchInlineSnapshot(`
{
"-6": [
{
"insertAction": [
{
"position": 0,
"type": "__Card__:__snapshot_a94a8_test_74",
},
],
"removeAction": [],
"updateAction": [],
},
],
"-7": [
{
"insertAction": [
{
"position": 0,
"type": "__Card__:__snapshot_a94a8_test_74",
},
],
"removeAction": [],
"updateAction": [],
},
],
"-8": [
{
"insertAction": [
{
"position": 0,
"type": "__Card__:__snapshot_a94a8_test_74",
},
],
"removeAction": [],
"updateAction": [],
},
],
}
`);

elementTree.triggerComponentAtIndex(parentListRef, 0);
elementTree.triggerComponentAtIndex(parentListRef, 1);
// enqueue c1
elementTree.triggerEnqueueComponent(parentListRef, 0);
// c3 reuse c1
elementTree.triggerComponentAtIndex(parentListRef, 2);
// c1 re-create
elementTree.triggerComponentAtIndex(parentListRef, 0);
// should have 4 list-item now
expect(parentListRef).toMatchInlineSnapshot(`
<list
update-list-info={
[
{
"insertAction": [
{
"position": 0,
"type": "__Card__:__snapshot_a94a8_test_73",
},
{
"position": 1,
"type": "__Card__:__snapshot_a94a8_test_73",
},
{
"position": 2,
"type": "__Card__:__snapshot_a94a8_test_73",
},
],
"removeAction": [],
"updateAction": [],
},
]
}
>
<list-item>
<text>
<raw-text
text="s2"
/>
</text>
<list
update-list-info={
[
{
"insertAction": [
{
"position": 0,
"type": "__Card__:__snapshot_a94a8_test_74",
},
],
"removeAction": [],
"updateAction": [],
},
]
}
/>
</list-item>
<list-item>
<text>
<raw-text
text="s2"
/>
</text>
<list
update-list-info={
[
{
"insertAction": [
{
"position": 0,
"type": "__Card__:__snapshot_a94a8_test_74",
},
],
"removeAction": [],
"updateAction": [],
},
]
}
/>
</list-item>
<list-item>
<text>
<raw-text
text="s2"
/>
</text>
<list
update-list-info={
[
{
"insertAction": [
{
"position": 0,
"type": "__Card__:__snapshot_a94a8_test_74",
},
],
"removeAction": [],
"updateAction": [],
},
]
}
/>
</list-item>
<list-item>
<text>
<raw-text
text="s2"
/>
</text>
<list
update-list-info={
[
{
"insertAction": [
{
"position": 0,
"type": "__Card__:__snapshot_a94a8_test_74",
},
],
"removeAction": [
0,
],
"updateAction": [],
},
]
}
/>
</list-item>
</list>
`);
});

it('should clear attached lists & should flush during ensureElements', () => {
const s1 = __SNAPSHOT__(
<view>
Expand Down Expand Up @@ -4146,7 +4363,7 @@ describe('update-list-info profile', () => {
{
"args": {
"list id": "3",
"update list info": "{"insertAction":[{"position":0,"type":"__Card__:__snapshot_a94a8_test_76"},{"position":1,"type":"__Card__:__snapshot_a94a8_test_76"},{"position":2,"type":"__Card__:__snapshot_a94a8_test_76"}],"removeAction":[],"updateAction":[]}",
"update list info": "{"insertAction":[{"position":0,"type":"__Card__:__snapshot_a94a8_test_79"},{"position":1,"type":"__Card__:__snapshot_a94a8_test_79"},{"position":2,"type":"__Card__:__snapshot_a94a8_test_79"}],"removeAction":[],"updateAction":[]}",
},
},
],
Expand Down
7 changes: 7 additions & 0 deletions packages/react/runtime/src/snapshot.ts
Original file line number Diff line number Diff line change
Expand Up @@ -335,6 +335,13 @@ export class SnapshotInstance {
// In nested list scenarios, there are some `list` that are lazily created.
// We need to `flush` them during `ensureElements`.
// Also, `flush` is a safe operation since it checks if the `list` is in `__pendingListUpdates`.
if (__pendingListUpdates.values && !__pendingListUpdates.values[this.__id] && this.__firstChild !== null) {
let child: SnapshotInstance | null = this.__firstChild;
while (child) {
(__pendingListUpdates.values[this.__id] ??= new ListUpdateInfoRecording(this)).onInsertBefore(child);
child = child.__nextSibling;
}
}
__pendingListUpdates.flushWithId(this.__id);
} else {
let index = 0;
Expand Down
Loading