Skip to content

Commit

Permalink
fix: replace inline component with component$ with the same key
Browse files Browse the repository at this point in the history
  • Loading branch information
Varixo committed Feb 23, 2025
1 parent 2ec1a82 commit 513c9ab
Show file tree
Hide file tree
Showing 3 changed files with 86 additions and 4 deletions.
5 changes: 5 additions & 0 deletions .changeset/mean-parents-buy.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@qwik.dev/core': patch
---

fix: replace inline component with component$ with the same key
12 changes: 8 additions & 4 deletions packages/qwik/src/core/client/vnode-diff.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1079,10 +1079,8 @@ export const vnode_diff = (
shouldRender = true;
} else if (!hashesAreEqual) {
insertNewComponent(host, componentQRL, jsxProps);
if (vNewNode) {
host = vNewNode as VirtualVNode;
shouldRender = true;
}
host = vNewNode as VirtualVNode;
shouldRender = true;
}

if (host) {
Expand All @@ -1103,6 +1101,7 @@ export const vnode_diff = (
const lookupKey = jsxNode.key;
const vNodeLookupKey = getKey(host);
const lookupKeysAreEqual = lookupKey === vNodeLookupKey;
const vNodeComponentHash = getComponentHash(host, container.$getObjectById$);

if (!lookupKeysAreEqual) {
// See if we already have this inline component later on.
Expand All @@ -1116,6 +1115,11 @@ export const vnode_diff = (
}
host = vNewNode as VirtualVNode;
}
// inline components don't have component hash - q:renderFn prop, so it should be null
else if (vNodeComponentHash != null) {
insertNewInlineComponent();
host = vNewNode as VirtualVNode;
}

if (host) {
let componentHost: VNode | null = host;
Expand Down
73 changes: 73 additions & 0 deletions packages/qwik/src/core/tests/inline-component.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,10 @@ const InlineWrapper = () => {

const Id = (props: any) => <div>Id: {props.id}</div>;

const ChildInline = () => {
return <div>Child inline</div>;
};

describe.each([
{ render: ssrRenderToDom }, //
{ render: domRender }, //
Expand Down Expand Up @@ -631,4 +635,73 @@ describe.each([
</InlineComponent>
);
});

it('should render swap component$ and inline component with the same key', async () => {
const Child = component$(() => {
return <div>Child component$</div>;
});

const Parent = component$(() => {
const toggle = useSignal(true);
return (
<>
<button onClick$={() => (toggle.value = !toggle.value)}></button>
{/* same key, simulate different routes and files, but the same keys at the same place */}
{toggle.value ? <ChildInline key="samekey" /> : <Child key="samekey" />}
</>
);
});

const { vNode, document } = await render(<Parent />, { debug });

expect(vNode).toMatchVDOM(
<Component ssr-required>
<Fragment ssr-required>
<button></button>
<InlineComponent ssr-required>
<div>Child inline</div>
</InlineComponent>
</Fragment>
</Component>
);

await trigger(document.body, 'button', 'click');

expect(vNode).toMatchVDOM(
<Component ssr-required>
<Fragment ssr-required>
<button></button>
<Component ssr-required>
<div>Child component$</div>
</Component>
</Fragment>
</Component>
);

await trigger(document.body, 'button', 'click');

expect(vNode).toMatchVDOM(
<Component ssr-required>
<Fragment ssr-required>
<button></button>
<InlineComponent ssr-required>
<div>Child inline</div>
</InlineComponent>
</Fragment>
</Component>
);

await trigger(document.body, 'button', 'click');

expect(vNode).toMatchVDOM(
<Component ssr-required>
<Fragment ssr-required>
<button></button>
<Component ssr-required>
<div>Child component$</div>
</Component>
</Fragment>
</Component>
);
});
});

0 comments on commit 513c9ab

Please sign in to comment.