From 9c1dafd17c26f33b5187426fe373fbadc7c14284 Mon Sep 17 00:00:00 2001
From: Qingyu Wang <40660121+colinaaa@users.noreply.github.com>
Date: Wed, 7 May 2025 23:21:12 +0800
Subject: [PATCH] fix(react/runtime): resolve race condition in `runWithForce`
---
.changeset/new-bobcats-cry.md | 7 ++++++
.../react/runtime/src/lynx/runWithForce.ts | 23 ++++++++++++++-----
2 files changed, 24 insertions(+), 6 deletions(-)
create mode 100644 .changeset/new-bobcats-cry.md
diff --git a/.changeset/new-bobcats-cry.md b/.changeset/new-bobcats-cry.md
new file mode 100644
index 0000000000..e764d6fde4
--- /dev/null
+++ b/.changeset/new-bobcats-cry.md
@@ -0,0 +1,7 @@
+---
+"@lynx-js/react": patch
+---
+
+Fixed a race condition when updating states and GlobalProps simultaneously.
+
+This fix prevents the "Attempt to render more than one ``" error from occurring during normal application usage.
diff --git a/packages/react/runtime/src/lynx/runWithForce.ts b/packages/react/runtime/src/lynx/runWithForce.ts
index 53d429b5ba..88b864f0d0 100644
--- a/packages/react/runtime/src/lynx/runWithForce.ts
+++ b/packages/react/runtime/src/lynx/runWithForce.ts
@@ -2,13 +2,17 @@ import { options } from 'preact';
import type { VNode } from 'preact';
import { COMPONENT, DIFF, DIFFED, FORCE } from '../renderToOpcodes/constants.js';
+const sForcedVNode = Symbol('FORCE');
+
+type PatchedVNode = VNode & { [sForcedVNode]?: true };
+
export function runWithForce(cb: () => void): void {
// save vnode and its `_component` in WeakMap
const m = new WeakMap();
const oldDiff = options[DIFF];
- options[DIFF] = (vnode: VNode) => {
+ options[DIFF] = (vnode: PatchedVNode) => {
if (oldDiff) {
oldDiff(vnode);
}
@@ -28,19 +32,26 @@ export function runWithForce(cb: () => void): void {
return m.get(vnode);
},
});
+ vnode[sForcedVNode] = true;
};
const oldDiffed = options[DIFFED];
- options[DIFFED] = (vnode: VNode) => {
+ options[DIFFED] = (vnode: PatchedVNode) => {
if (oldDiffed) {
oldDiffed(vnode);
}
- // delete is a reverse operation of previous `Object.defineProperty`
- delete vnode[COMPONENT];
- // restore
- vnode[COMPONENT] = m.get(vnode);
+ // There would be cases when `options[DIFF]` has been reset while options[DIFFED] is not,
+ // so we need to check if `vnode` is patched by `options[DIFF]`.
+ // We only want to change the patched vnode
+ if (vnode[sForcedVNode]) {
+ // delete is a reverse operation of previous `Object.defineProperty`
+ delete vnode[COMPONENT];
+ delete vnode[sForcedVNode];
+ // restore
+ vnode[COMPONENT] = m.get(vnode);
+ }
};
try {