Skip to content

Commit 43846be

Browse files
JerryWu1234wuls
and
wuls
authored
fix: the useOn hook not working with Slot (#7250)
* fix: enhance slot rendering and add useOnDocument test * Create old-mayflies-fetch.md * fix wrong name * optimize the code * remove redundancy code * remove redundancy code * fix: solve all problems that mentioned * remove rebundant code * directly return value --------- Co-authored-by: wuls <[email protected]>
1 parent f5326c3 commit 43846be

File tree

4 files changed

+101
-10
lines changed

4 files changed

+101
-10
lines changed

.changeset/old-mayflies-fetch.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@qwik.dev/core': patch
3+
---
4+
5+
fix: the use hook didn't work when type is Slot.

packages/qwik/src/core/shared/component-execution.ts

+20-10
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { isDev } from '@qwik.dev/core/build';
22
import { isQwikComponent, type OnRenderFn } from './component.public';
33
import { assertDefined } from './error/assert';
44
import { isQrl, type QRLInternal } from './qrl/qrl-class';
5-
import { JSXNodeImpl, isJSXNode, type Props } from './jsx/jsx-runtime';
5+
import { Fragment, JSXNodeImpl, _jsxSorted, isJSXNode, type Props } from './jsx/jsx-runtime';
66
import type { JSXNodeInternal, JSXOutput } from './jsx/types/jsx-node';
77
import type { KnownEventNames } from './jsx/types/jsx-qwik-events';
88
import { invokeApply, newInvokeContext, untrack } from '../use/use-core';
@@ -23,6 +23,7 @@ import { logWarn } from './utils/log';
2323
import { EffectProperty, isSignal } from '../signal/signal';
2424
import { vnode_isVNode } from '../client/vnode';
2525
import { clearVNodeEffectDependencies } from '../signal/signal-subscriber';
26+
import { Slot } from '../shared/jsx/slot.public';
2627

2728
/**
2829
* Use `executeComponent` to execute a component.
@@ -101,7 +102,7 @@ export const executeComponent = (
101102
(jsx) => {
102103
const useOnEvents = container.getHostProp<UseOnMap>(renderHost, USE_ON_LOCAL);
103104
if (useOnEvents) {
104-
return maybeThen(addUseOnEvents(jsx, useOnEvents), () => jsx);
105+
return addUseOnEvents(jsx, useOnEvents);
105106
}
106107
return jsx;
107108
},
@@ -133,8 +134,9 @@ export const executeComponent = (
133134
function addUseOnEvents(
134135
jsx: JSXOutput,
135136
useOnEvents: UseOnMap
136-
): ValueOrPromise<JSXNodeInternal<string> | null> {
137+
): ValueOrPromise<JSXNodeInternal<string> | null | JSXOutput> {
137138
const jsxElement = findFirstStringJSX(jsx);
139+
let jsxResult = jsx;
138140
return maybeThen(jsxElement, (jsxElement) => {
139141
let isInvisibleComponent = false;
140142
if (!jsxElement) {
@@ -153,12 +155,14 @@ function addUseOnEvents(
153155
if (Object.prototype.hasOwnProperty.call(useOnEvents, key)) {
154156
if (isInvisibleComponent) {
155157
if (key === 'onQvisible$') {
156-
jsxElement = addScriptNodeForInvisibleComponents(jsx);
158+
const [jsxElement, jsx] = addScriptNodeForInvisibleComponents(jsxResult);
159+
jsxResult = jsx;
157160
if (jsxElement) {
158161
addUseOnEvent(jsxElement, 'document:onQinit$', useOnEvents[key]);
159162
}
160163
} else if (key.startsWith('document:') || key.startsWith('window:')) {
161-
jsxElement = addScriptNodeForInvisibleComponents(jsx);
164+
const [jsxElement, jsx] = addScriptNodeForInvisibleComponents(jsxResult);
165+
jsxResult = jsx;
162166
if (jsxElement) {
163167
addUseOnEvent(jsxElement, key, useOnEvents[key]);
164168
}
@@ -176,7 +180,7 @@ function addUseOnEvents(
176180
}
177181
}
178182
}
179-
return jsxElement;
183+
return jsxResult;
180184
});
181185
}
182186

@@ -221,7 +225,9 @@ function findFirstStringJSX(jsx: JSXOutput): ValueOrPromise<JSXNodeInternal<stri
221225
return null;
222226
}
223227

224-
function addScriptNodeForInvisibleComponents(jsx: JSXOutput): JSXNodeInternal<string> | null {
228+
function addScriptNodeForInvisibleComponents(
229+
jsx: JSXOutput
230+
): [JSXNodeInternal<string> | null, JSXOutput | null] {
225231
if (isJSXNode(jsx)) {
226232
const jsxElement = new JSXNodeImpl(
227233
'script',
@@ -233,6 +239,9 @@ function addScriptNodeForInvisibleComponents(jsx: JSXOutput): JSXNodeInternal<st
233239
null,
234240
3
235241
);
242+
if (jsx.type === Slot) {
243+
return [jsxElement, _jsxSorted(Fragment, null, null, [jsx, jsxElement], 0, null)];
244+
}
236245

237246
if (jsx.children == null) {
238247
jsx.children = jsxElement;
@@ -241,11 +250,12 @@ function addScriptNodeForInvisibleComponents(jsx: JSXOutput): JSXNodeInternal<st
241250
} else {
242251
jsx.children = [jsx.children, jsxElement];
243252
}
244-
return jsxElement;
253+
return [jsxElement, jsx];
245254
} else if (Array.isArray(jsx) && jsx.length) {
246255
// get first element
247-
return addScriptNodeForInvisibleComponents(jsx[0]);
256+
const [jsxElement, _] = addScriptNodeForInvisibleComponents(jsx[0]);
257+
return [jsxElement, jsx];
248258
}
249259

250-
return null;
260+
return [null, null];
251261
}

packages/qwik/src/core/ssr/ssr-render-jsx.ts

+2
Original file line numberDiff line numberDiff line change
@@ -245,6 +245,7 @@ function processJSXNode(
245245
const node = ssr.getLastNode();
246246
const slotName = getSlotName(host, jsx, ssr);
247247
projectionAttrs.push(QSlot, slotName);
248+
248249
enqueue(new ParentComponentData(options.styleScoped, options.parentComponentFrame));
249250
enqueue(ssr.closeProjection);
250251
const slotDefaultChildren: JSXChildren | null = jsx.children || null;
@@ -300,6 +301,7 @@ function processJSXNode(
300301
options.styleScoped,
301302
options.parentComponentFrame
302303
);
304+
303305
const jsxOutput = applyQwikComponentBody(ssr, jsx, type);
304306
const compStyleComponentId = addComponentStylePrefix(host.getProp(QScopedStyle));
305307
enqueue(new ParentComponentData(options.styleScoped, options.parentComponentFrame));

packages/qwik/src/core/tests/use-on.spec.tsx

+74
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,11 @@ import {
22
$,
33
Fragment as Awaited,
44
Fragment as Component,
5+
Fragment as Projection,
56
component$,
67
Fragment,
78
Fragment as Signal,
9+
Slot,
810
useOn,
911
useOnDocument,
1012
useOnWindow,
@@ -647,4 +649,76 @@ describe.each([
647649
await trigger(document.body, 'div', 'click');
648650
await expect(document.querySelector('div')).toMatchDOM(<div>1</div>);
649651
});
652+
653+
it('#7230 - when multiple useOn are used in a component that is not rendered, it should add multiple script nodes', async () => {
654+
const BreakpointProvider = component$(() => {
655+
useOnDocument(
656+
'click',
657+
$(() => {})
658+
);
659+
660+
useOnWindow(
661+
'resize',
662+
$(() => {})
663+
);
664+
665+
useVisibleTask$(() => {});
666+
667+
return <Slot />;
668+
});
669+
670+
const LayoutTest = component$(() => {
671+
return (
672+
<BreakpointProvider>
673+
<div>test</div>
674+
</BreakpointProvider>
675+
);
676+
});
677+
const { vNode } = await render(<LayoutTest />, { debug });
678+
expect(vNode).toMatchVDOM(
679+
<Component ssr-required>
680+
<Component ssr-required>
681+
<Component ssr-required>
682+
<Component ssr-required>
683+
<div>test</div>
684+
</Component>
685+
<script type="placeholder" hidden></script>
686+
<script type="placeholder" hidden></script>
687+
<script type="placeholder" hidden></script>
688+
</Component>
689+
</Component>
690+
</Component>
691+
);
692+
});
693+
it('#7230 - when useOnDocument is used in a component that is not rendered, it should add a script node', async () => {
694+
const BreakpointProvider = component$(() => {
695+
useOnDocument(
696+
'click',
697+
$(() => {})
698+
);
699+
700+
return <Slot />;
701+
});
702+
703+
const Layout = component$(() => {
704+
return (
705+
<BreakpointProvider>
706+
<div>test</div>
707+
</BreakpointProvider>
708+
);
709+
});
710+
const { vNode } = await render(<Layout />, { debug });
711+
expect(vNode).toMatchVDOM(
712+
<Component ssr-required>
713+
<Component ssr-required>
714+
<Component ssr-required>
715+
<Projection ssr-required>
716+
<div>test</div>
717+
</Projection>
718+
<script type="placeholder" hidden></script>
719+
</Component>
720+
</Component>
721+
</Component>
722+
);
723+
});
650724
});

0 commit comments

Comments
 (0)