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 {