]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
fix(suspense): fix nested async child toggle inside already resovled suspense
authorEvan You <yyx990803@gmail.com>
Thu, 26 Nov 2020 16:06:55 +0000 (11:06 -0500)
committerEvan You <yyx990803@gmail.com>
Thu, 26 Nov 2020 16:06:55 +0000 (11:06 -0500)
fix #2215

packages/runtime-core/__tests__/components/Suspense.spec.ts
packages/runtime-core/src/components/Suspense.ts

index e980047553298c04ac665c7d9b31b9f1d24f0026..bf209007d9ecbd3fdba5b74e4502edeee57ae298 100644 (file)
@@ -1093,4 +1093,36 @@ describe('Suspense', () => {
     await nextTick()
     expect(root.innerHTML).toBe(`<div><span>2</span></div>`)
   })
+
+  // #2215
+  test('toggling nested async setup component inside already resolved suspense', async () => {
+    const toggle = ref(false)
+    const Child = {
+      async setup() {
+        return () => h('div', 'child')
+      }
+    }
+    const Parent = () => h('div', ['parent', toggle.value ? h(Child) : null])
+    const Comp = {
+      setup() {
+        return () => h(Suspense, () => h(Parent))
+      }
+    }
+
+    const root = nodeOps.createElement('div')
+    render(h(Comp), root)
+    expect(serializeInner(root)).toBe(`<div>parent<!----></div>`)
+
+    toggle.value = true
+    // wait for flush
+    await nextTick()
+    // wait for child async setup resolve
+    await nextTick()
+    // child should be rendered now instead of stuck in limbo
+    expect(serializeInner(root)).toBe(`<div>parent<div>child</div></div>`)
+
+    toggle.value = false
+    await nextTick()
+    expect(serializeInner(root)).toBe(`<div>parent<!----></div>`)
+  })
 })
index 105af7315317f6b374b96f3369883ea1b937b433..1b5adfed3505e52423e29ceeb249d6c12b8fa985 100644 (file)
@@ -542,12 +542,11 @@ function createSuspenseBoundary(
     },
 
     registerDep(instance, setupRenderEffect) {
-      if (!suspense.pendingBranch) {
-        return
+      const isInPendingSuspense = !!suspense.pendingBranch
+      if (isInPendingSuspense) {
+        suspense.deps++
       }
-
       const hydratedEl = instance.vnode.el
-      suspense.deps++
       instance
         .asyncDep!.catch(err => {
           handleError(err, instance, ErrorCodes.SETUP_FUNCTION)
@@ -562,7 +561,6 @@ function createSuspenseBoundary(
           ) {
             return
           }
-          suspense.deps--
           // retry from this component
           instance.asyncResolved = true
           const { vnode } = instance
@@ -597,7 +595,8 @@ function createSuspenseBoundary(
           if (__DEV__) {
             popWarningContext()
           }
-          if (suspense.deps === 0) {
+          // only decrease deps count if suspense is not already resolved
+          if (isInPendingSuspense && --suspense.deps === 0) {
             suspense.resolve()
           }
         })