Skip to content

Commit 44996d1

Browse files
committed
fix(suspense): fix suspense regression for errored template component
fix #3857
1 parent c7efb96 commit 44996d1

File tree

2 files changed

+54
-8
lines changed

2 files changed

+54
-8
lines changed

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

+43
Original file line numberDiff line numberDiff line change
@@ -632,6 +632,49 @@ describe('Suspense', () => {
632632
expect(serializeInner(root)).toBe(`<div>oops</div>`)
633633
})
634634

635+
// #3857
636+
test('error handling w/ template optimization', async () => {
637+
const Async = {
638+
async setup() {
639+
throw new Error('oops')
640+
}
641+
}
642+
643+
const Comp = {
644+
template: `
645+
<div v-if="errorMessage">{{ errorMessage }}</div>
646+
<Suspense v-else>
647+
<div>
648+
<Async />
649+
</div>
650+
<template #fallback>
651+
<div>fallback</div>
652+
</template>
653+
</Suspense>
654+
`,
655+
components: { Async },
656+
setup() {
657+
const errorMessage = ref<string | null>(null)
658+
onErrorCaptured(err => {
659+
errorMessage.value =
660+
err instanceof Error
661+
? err.message
662+
: `A non-Error value thrown: ${err}`
663+
return false
664+
})
665+
return { errorMessage }
666+
}
667+
}
668+
669+
const root = nodeOps.createElement('div')
670+
render(h(Comp), root)
671+
expect(serializeInner(root)).toBe(`<div>fallback</div>`)
672+
673+
await Promise.all(deps)
674+
await nextTick()
675+
expect(serializeInner(root)).toBe(`<div>oops</div>`)
676+
})
677+
635678
it('combined usage (nested async + nested suspense + multiple deps)', async () => {
636679
const msg = ref('nested msg')
637680
const calls: number[] = []

packages/runtime-core/src/renderer.ts

+11-8
Original file line numberDiff line numberDiff line change
@@ -1073,16 +1073,19 @@ function baseCreateRenderer(
10731073
const newVNode = newChildren[i]
10741074
// Determine the container (parent element) for the patch.
10751075
const container =
1076+
// oldVNode may be an errored async setup() component inside Suspense
1077+
// which will not have a mounted element
1078+
oldVNode.el &&
10761079
// - In the case of a Fragment, we need to provide the actual parent
10771080
// of the Fragment itself so it can move its children.
1078-
oldVNode.type === Fragment ||
1079-
// - In the case of different nodes, there is going to be a replacement
1080-
// which also requires the correct parent container
1081-
!isSameVNodeType(oldVNode, newVNode) ||
1082-
// - In the case of a component, it could contain anything.
1083-
oldVNode.shapeFlag & ShapeFlags.COMPONENT ||
1084-
oldVNode.shapeFlag & ShapeFlags.TELEPORT
1085-
? hostParentNode(oldVNode.el!)!
1081+
(oldVNode.type === Fragment ||
1082+
// - In the case of different nodes, there is going to be a replacement
1083+
// which also requires the correct parent container
1084+
!isSameVNodeType(oldVNode, newVNode) ||
1085+
// - In the case of a component, it could contain anything.
1086+
oldVNode.shapeFlag & ShapeFlags.COMPONENT ||
1087+
oldVNode.shapeFlag & ShapeFlags.TELEPORT)
1088+
? hostParentNode(oldVNode.el)!
10861089
: // In other cases, the parent container is not actually used so we
10871090
// just pass the block element here to avoid a DOM parentNode call.
10881091
fallbackContainer

0 commit comments

Comments
 (0)