diff --git a/packages/runtime-core/src/apiWatch.ts b/packages/runtime-core/src/apiWatch.ts index 85b3fff9792..9c791689ed6 100644 --- a/packages/runtime-core/src/apiWatch.ts +++ b/packages/runtime-core/src/apiWatch.ts @@ -223,7 +223,7 @@ function doWatch( } let cleanup: () => void - const onInvalidate: InvalidateCbRegistrator = (fn: () => void) => { + let onInvalidate: InvalidateCbRegistrator = (fn: () => void) => { cleanup = runner.options.onStop = () => { callWithErrorHandling(fn, instance, ErrorCodes.WATCH_CLEANUP) } @@ -232,6 +232,8 @@ function doWatch( // in SSR there is no need to setup an actual effect, and it should be noop // unless it's eager if (__NODE_JS__ && isInSSRComponentSetup) { + // we will also not call the invalidate callback (+ runner is not set up) + onInvalidate = NOOP if (!cb) { getter() } else if (immediate) { diff --git a/packages/server-renderer/__tests__/render.spec.ts b/packages/server-renderer/__tests__/render.spec.ts index e27fdf4bb56..e2ec13c0344 100644 --- a/packages/server-renderer/__tests__/render.spec.ts +++ b/packages/server-renderer/__tests__/render.spec.ts @@ -8,9 +8,10 @@ import { defineComponent, createTextVNode, createStaticVNode, - KeepAlive, withCtx, - Transition + KeepAlive, + Transition, + watchEffect } from 'vue' import { escapeHtml } from '@vue/shared' import { renderToString } from '../src/renderToString' @@ -775,5 +776,15 @@ function testRender(type: string, render: typeof renderToString) { const html = await render(app) expect(html).toBe(`
hello
`) }) + + // https://github.com/vuejs/vue-next/issues/3322 + test('effect onInvalidate does not error', async () => { + const noop = () => {} + const app = createApp({ + setup: () => watchEffect(onInvalidate => onInvalidate(noop)), + render: noop, + }) + expect(await render(app)).toBe('') + }) }) }