Skip to content

fix(runtime-core): properly handle async component update before resolve#11619

Merged
edison1105 merged 8 commits intovuejs:mainfrom
linzhe141:fix-async-component
Feb 9, 2026
Merged

fix(runtime-core): properly handle async component update before resolve#11619
edison1105 merged 8 commits intovuejs:mainfrom
linzhe141:fix-async-component

Conversation

@linzhe141
Copy link
Copy Markdown
Member

@linzhe141 linzhe141 commented Aug 15, 2024

close #11617

this pr playground

Summary by CodeRabbit

  • Bug Fixes

    • Improved async component update scheduling so prop updates are deferred and applied reliably after render, preventing lost or out-of-order updates in suspended components.
  • Tests

    • Added a test verifying multiple prop updates to an async component both before and after it resolves, ensuring correct update propagation through suspense boundaries.

@github-actions
Copy link
Copy Markdown

github-actions bot commented Aug 15, 2024

Size Report

Bundles

File Size Gzip Brotli
runtime-dom.global.prod.js 104 kB (+4 B) 39.3 kB (+1 B) 35.4 kB (-14 B)
vue.global.prod.js 162 kB (+4 B) 59.3 kB (+3 B) 52.8 kB (+19 B)

Usages

Name Size Gzip Brotli
createApp (CAPI only) 47.2 kB (+4 B) 18.4 kB (-2 B) 16.9 kB (+4 B)
createApp 55.3 kB (+6 B) 21.5 kB (-5 B) 19.6 kB (+3 B)
createSSRApp 59.6 kB (+4 B) 23.2 kB (-1 B) 21.2 kB (+3 B)
defineCustomElement 60.9 kB (+4 B) 23.2 kB 21.2 kB (-1 B)
overall 69.7 kB (+4 B) 26.7 kB 24.4 kB (-58 B)

@linzhe141 linzhe141 marked this pull request as draft August 15, 2024 05:19
@linzhe141 linzhe141 marked this pull request as ready for review August 15, 2024 05:22
// the instance may be destroyed during the time period
if (!instance.isUnmounted) {
componentUpdateFn()
instance.update()
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
instance.update()
update()

@edison1105
Copy link
Copy Markdown
Member

Thank you for addressing the issue.
Could you please add a test case?

@edison1105 edison1105 added need test The PR has missing test cases. 🔨 p3-minor-bug Priority 3: this fixes a bug, but is an edge case that only affects very specific usage. labels Aug 15, 2024
// the instance may be destroyed during the time period
if (!instance.isUnmounted) {
componentUpdateFn()
update()
Copy link
Copy Markdown
Member

@edison1105 edison1105 Aug 15, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is another problem here. The update() is executed 2 times.
This behavior is not related to this PR. But it needs to be fixed.

The flow is as follows:

  1. When locateNonHydratedAsyncRoot is called, subComponent.asyncResolved is false, nonHydratedAsyncRoot has a value, and a then callback is registered.
  2. The then callback is triggered and calls update.
  3. We go back to step 1, but subComponent.asyncResolved is still false because the then at this line is registered later and executes after the previously registered then. The then callback is registered again.
  4. Same as step 2.
  5. Now subComponent.asyncResolved is true, and nonHydratedAsyncRoot is undefined.
  6. The remaining update logic runs.

In this process, the then callback gets registered twice, and update is called twice.
The following code can be used to prevent this behavior.

nonHydratedAsyncRoot.asyncDep!.then(() => {
  // the instance may be destroyed during the time period
  queuePostRenderEffect(()=>{
    if (!instance.isUnmounted) update()
  },parentSuspense)
})

@edison1105
Copy link
Copy Markdown
Member

edison1105 commented Aug 15, 2024

I'm pretty sure the previous fix is proper.
Is there any reason why you changed it?

@linzhe141
Copy link
Copy Markdown
Member Author

@edison1105 I tried to fix it again, maybe it should be handled like this

I'm pretty sure the previous fix is proper.我很确定之前的修复是正确的。 Is there any reason why you changed it?你有什么理由改变它吗?

You are right! I was wrong. I thought that when patching suspense, I had to confirm which component was pending, but this has nothing to do with pendingBranch.

@edison1105 edison1105 changed the title fix(runtime-core): fix nonHydratedAsyncRoot update fix(runtime-core): properly handle async component update before resolve Aug 15, 2024
@edison1105 edison1105 added ready for review This PR requires more reviews and removed need test The PR has missing test cases. labels Aug 15, 2024
@pkg-pr-new
Copy link
Copy Markdown

pkg-pr-new bot commented Jan 3, 2025

Open in StackBlitz

@vue/compiler-core

pnpm add https://pkg.pr.new/@vue/compiler-core@11619
npm i https://pkg.pr.new/@vue/compiler-core@11619
yarn add https://pkg.pr.new/@vue/compiler-core@11619.tgz

@vue/compiler-dom

pnpm add https://pkg.pr.new/@vue/compiler-dom@11619
npm i https://pkg.pr.new/@vue/compiler-dom@11619
yarn add https://pkg.pr.new/@vue/compiler-dom@11619.tgz

@vue/compiler-sfc

pnpm add https://pkg.pr.new/@vue/compiler-sfc@11619
npm i https://pkg.pr.new/@vue/compiler-sfc@11619
yarn add https://pkg.pr.new/@vue/compiler-sfc@11619.tgz

@vue/compiler-ssr

pnpm add https://pkg.pr.new/@vue/compiler-ssr@11619
npm i https://pkg.pr.new/@vue/compiler-ssr@11619
yarn add https://pkg.pr.new/@vue/compiler-ssr@11619.tgz

@vue/reactivity

pnpm add https://pkg.pr.new/@vue/reactivity@11619
npm i https://pkg.pr.new/@vue/reactivity@11619
yarn add https://pkg.pr.new/@vue/reactivity@11619.tgz

@vue/runtime-core

pnpm add https://pkg.pr.new/@vue/runtime-core@11619
npm i https://pkg.pr.new/@vue/runtime-core@11619
yarn add https://pkg.pr.new/@vue/runtime-core@11619.tgz

@vue/runtime-dom

pnpm add https://pkg.pr.new/@vue/runtime-dom@11619
npm i https://pkg.pr.new/@vue/runtime-dom@11619
yarn add https://pkg.pr.new/@vue/runtime-dom@11619.tgz

@vue/server-renderer

pnpm add https://pkg.pr.new/@vue/server-renderer@11619
npm i https://pkg.pr.new/@vue/server-renderer@11619
yarn add https://pkg.pr.new/@vue/server-renderer@11619.tgz

@vue/shared

pnpm add https://pkg.pr.new/@vue/shared@11619
npm i https://pkg.pr.new/@vue/shared@11619
yarn add https://pkg.pr.new/@vue/shared@11619.tgz

vue

pnpm add https://pkg.pr.new/vue@11619
npm i https://pkg.pr.new/vue@11619
yarn add https://pkg.pr.new/vue@11619.tgz

@vue/compat

pnpm add https://pkg.pr.new/@vue/compat@11619
npm i https://pkg.pr.new/@vue/compat@11619
yarn add https://pkg.pr.new/@vue/compat@11619.tgz

commit: fa65d5c

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented May 10, 2025

📝 Walkthrough

Walkthrough

A new test verifies prop updates to async components inside Suspense; renderer update logic was changed to defer post-resolution updates by scheduling them with queuePostRenderEffect instead of calling updates synchronously.

Changes

Cohort / File(s) Summary
Tests
packages/runtime-core/__tests__/components/Suspense.spec.ts
Added a test "update async component before resolve then update again" to exercise prop updates on an async component inside a Suspense boundary and assert onUpdated call sequence.
Renderer core
packages/runtime-core/src/renderer.ts
Deferred invoking component update for resolved async roots by scheduling the update with queuePostRenderEffect and guarding with mounted/unmounted checks and parent suspense linkage.

Sequence Diagram(s)

sequenceDiagram
    participant Parent as ParentComponent
    participant Suspense as SuspenseBoundary
    participant Async as AsyncChildComponent
    participant Renderer

    Parent->>Suspense: Render async child (dynamic)
    Suspense->>Async: Mount -> async setup (suspends)
    Parent->>Async: Prop update (before resolve)
    Async->>Renderer: Request update (while suspended)
    Renderer->>Renderer: queuePostRenderEffect(update) (defer)
    note right of Renderer: waits for async resolve
    Async-->>Suspense: Resolves
    Renderer->>Async: Run deferred update if still mounted
    Async-->>Parent: onUpdated / re-render completes
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Suggested reviewers

  • Doctor-wu

Poem

I hopped through Suspense with eager paws,
Watched props queue up and fix their laws.
Deferred by hops, resolved at last,
Updates arrive — no longer past.
A joyful thump — tests green and fast! 🐇✨

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately reflects the main change: fixing proper handling of async component updates before resolution in the runtime-core module.
Linked Issues check ✅ Passed The PR addresses issue #11617 by fixing async component prop update propagation within Suspense boundaries, implementing deferred update scheduling via queuePostRenderEffect.
Out of Scope Changes check ✅ Passed All changes are directly related to fixing the async component update issue: a test case validating the fix and the runtime fix in baseCreateRenderer's setupRenderEffect flow.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Comment @coderabbitai help to get the list of available commands and usage tips.

@edison1105
Copy link
Copy Markdown
Member

/ecosystem-ci run

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@packages/runtime-core/__tests__/components/Suspense.spec.ts`:
- Around line 2185-2241: Await the render and make the test deterministic by
using fake timers and advancing them so the async onMounted in Parent runs
before the test exits: call the test runtime's fake-timer setup (e.g.,
jest.useFakeTimers()/vi.useFakeTimers()), await render(h(App), root), advance
timers by 1000ms (e.g., jest.advanceTimersByTime(1000) or runAllTimers), then
await nextTick()/microtask flush before checking the expectation inside Parent's
onMounted (the expect(arr) assertion) or resolve a done promise after the
assertion so the test waits for completion; update the test around Parent, App,
and the render call to implement these changes.

@vue-bot
Copy link
Copy Markdown
Contributor

vue-bot commented Feb 9, 2026

📝 Ran ecosystem CI: Open

suite result latest scheduled
quasar success success
nuxt success success
radix-vue success success
pinia success success
primevue success success
vite-plugin-vue success success
vant success success
language-tools success success
router failure failure
vue-i18n success success
test-utils success success
vuetify success success
vitepress success success
vueuse success success
vue-simple-compiler success success
vue-macros success success

@edison1105 edison1105 merged commit e71c26c into vuejs:main Feb 9, 2026
13 of 14 checks passed
@linzhe141 linzhe141 deleted the fix-async-component branch February 9, 2026 14:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

🔨 p3-minor-bug Priority 3: this fixes a bug, but is an edge case that only affects very specific usage. ready for review This PR requires more reviews scope: suspense

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Async Component inside Suspense doesn't receive prop update

3 participants