diff --git a/.changeset/nine-chairs-care.md b/.changeset/nine-chairs-care.md
new file mode 100644
index 0000000000..d87b7d5b36
--- /dev/null
+++ b/.changeset/nine-chairs-care.md
@@ -0,0 +1,5 @@
+---
+"@lynx-js/react": patch
+---
+
+Fix a memory leak when using `
`.
diff --git a/packages/react/runtime/src/list.ts b/packages/react/runtime/src/list.ts
index 9e94b4cca8..6f3aed7ee9 100644
--- a/packages/react/runtime/src/list.ts
+++ b/packages/react/runtime/src/list.ts
@@ -6,6 +6,7 @@ import type { SnapshotInstance } from './snapshot.js';
export const gSignMap: Record> = {};
export const gRecycleMap: Record>> = {};
+const gParentWeakMap: WeakMap = new WeakMap();
export function clearListGlobal(): void {
for (const key in gSignMap) {
@@ -20,6 +21,23 @@ export function componentAtIndexFactory(
ctx: SnapshotInstance[],
hydrateFunction: (before: SnapshotInstance, after: SnapshotInstance) => void,
): [ComponentAtIndexCallback, ComponentAtIndexesCallback] {
+ // A hack workaround to ensure childCtx has no direct reference through `__parent` to list,
+ // to avoid memory leak.
+ // TODO(hzy): make `__parent` a WeakRef or `#__parent` in the future.
+ ctx.forEach((childCtx) => {
+ if (gParentWeakMap.has(childCtx)) {
+ // do it only once
+ } else {
+ gParentWeakMap.set(childCtx, childCtx.parentNode!);
+ Object.defineProperty(childCtx, '__parent', {
+ get: () => gParentWeakMap.get(childCtx)!,
+ set: (value: unknown) => {
+ gParentWeakMap.set(childCtx, value);
+ },
+ });
+ }
+ });
+
const componentAtIndex = (
list: FiberElement,
listID: number,