diff --git a/.changeset/error-function-jsx-children.md b/.changeset/error-function-jsx-children.md
new file mode 100644
index 00000000000..a147f48f26c
--- /dev/null
+++ b/.changeset/error-function-jsx-children.md
@@ -0,0 +1,5 @@
+---
+'@builder.io/qwik': patch
+---
+
+Throw an error when a function is passed as a JSX child instead of silently skipping it. This restores the v1 behavior where passing `{myFn}` instead of `{myFn()}` would produce a clear error message.
diff --git a/packages/qwik/src/core/render/dom/render-dom.ts b/packages/qwik/src/core/render/dom/render-dom.ts
index 916469a54aa..44248f1d9b5 100644
--- a/packages/qwik/src/core/render/dom/render-dom.ts
+++ b/packages/qwik/src/core/render/dom/render-dom.ts
@@ -176,6 +176,12 @@ export const processData = (
return node.then((node) => processData(node, invocationContext));
} else if (node === SkipRender) {
return new ProcessedJSXNodeImpl(SKIP_RENDER_TYPE, EMPTY_OBJ, null, EMPTY_ARRAY, 0, null);
+ } else if (isFunction(node)) {
+ throw new Error(
+ `Functions are not valid JSX children. ` +
+ `You might have passed a function instead of calling it: use {fn()} instead of {fn}. ` +
+ `If this is a component, wrap it with component$().`
+ );
} else {
logWarn('A unsupported value was passed to the JSX, skipping render. Value:', node);
return undefined;
diff --git a/packages/qwik/src/core/render/dom/render.unit.tsx b/packages/qwik/src/core/render/dom/render.unit.tsx
index 60fc11825a1..ddaf0d47f2b 100644
--- a/packages/qwik/src/core/render/dom/render.unit.tsx
+++ b/packages/qwik/src/core/render/dom/render.unit.tsx
@@ -44,6 +44,16 @@ test('should only render string/number', async () => {
await expectRendered(fixture, '
string123
');
});
+test('should throw error when function is passed as JSX child', async () => {
+ const fixture = new ElementFixture();
+ try {
+ await render(fixture.host, {() => hello}
);
+ assert.fail('Expected an error to be thrown');
+ } catch (e: any) {
+ assert.match(e.message, /is not an accepted value|Functions are not valid JSX children/);
+ }
+});
+
test('should serialize events correctly', async () => {
const fixture = new ElementFixture();
await render(
diff --git a/packages/qwik/src/core/render/ssr/render-ssr.ts b/packages/qwik/src/core/render/ssr/render-ssr.ts
index 07bcf397326..54edfd72077 100644
--- a/packages/qwik/src/core/render/ssr/render-ssr.ts
+++ b/packages/qwik/src/core/render/ssr/render-ssr.ts
@@ -923,6 +923,12 @@ const processData = (
} else if (isPromise(node)) {
stream.write(FLUSH_COMMENT);
return node.then((node) => processData(node, rCtx, ssrCtx, stream, flags, beforeClose));
+ } else if (isFunction(node)) {
+ throw new Error(
+ `Functions are not valid JSX children. ` +
+ `You might have passed a function instead of calling it: use {fn()} instead of {fn}. ` +
+ `If this is a component, wrap it with component$().`
+ );
} else {
logWarn('A unsupported value was passed to the JSX, skipping render. Value:', node);
return;
diff --git a/packages/qwik/src/core/render/ssr/render-ssr.unit.tsx b/packages/qwik/src/core/render/ssr/render-ssr.unit.tsx
index ebd7b01035f..d5cb750f113 100644
--- a/packages/qwik/src/core/render/ssr/render-ssr.unit.tsx
+++ b/packages/qwik/src/core/render/ssr/render-ssr.unit.tsx
@@ -1968,6 +1968,11 @@ export const HtmlContext = component$(() => {
return ;
});
+test('should throw error when function is passed as JSX child', async () => {
+ const fn = () => jsx('div', { children: 'hello' });
+ await throws(() => testSSR(jsx('body', { children: [fn] }), ''));
+});
+
async function testSSR(
node: JSXOutput,
expected: string | string[],