Skip to content

Commit e38fff6

Browse files
authored
Merge pull request #7633 from QwikDev/v2-dont-rerender-empty-props
fix: proper empty props diffing
2 parents b7fabca + 5b3a514 commit e38fff6

File tree

3 files changed

+88
-1
lines changed

3 files changed

+88
-1
lines changed

.changeset/cold-rice-slide.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@qwik.dev/core': patch
3+
---
4+
5+
fix: proper empty props diffing

packages/qwik/src/core/client/vnode-diff.ts

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1237,7 +1237,12 @@ function getComponentHash(vNode: VNode | null, getObject: (id: string) => any):
12371237
function Projection() {}
12381238

12391239
function propsDiffer(src: Record<string, any>, dst: Record<string, any>): boolean {
1240-
if (!src || !dst) {
1240+
const srcEmpty = isPropsEmpty(src);
1241+
const dstEmpty = isPropsEmpty(dst);
1242+
if (srcEmpty && dstEmpty) {
1243+
return false;
1244+
}
1245+
if (srcEmpty || dstEmpty) {
12411246
return true;
12421247
}
12431248
let srcKeys = removePropsKeys(Object.keys(src), ['children', QBackRefs]);
@@ -1257,6 +1262,13 @@ function propsDiffer(src: Record<string, any>, dst: Record<string, any>): boolea
12571262
return false;
12581263
}
12591264

1265+
function isPropsEmpty(props: Record<string, any>): boolean {
1266+
if (!props) {
1267+
return true;
1268+
}
1269+
return Object.keys(props).length === 0;
1270+
}
1271+
12601272
function removePropsKeys(keys: string[], propKeys: string[]): string[] {
12611273
for (let i = propKeys.length - 1; i >= 0; i--) {
12621274
const propKey = propKeys[i];

packages/qwik/src/core/tests/component.spec.tsx

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -301,6 +301,76 @@ describe.each([
301301
);
302302
});
303303

304+
it('should not rerender component with empty props', async () => {
305+
(globalThis as any).componentExecuted = [];
306+
const Component1 = component$<PropsOf<any>>(() => {
307+
(globalThis as any).componentExecuted.push('Component1');
308+
return <div></div>;
309+
});
310+
const Parent = component$(() => {
311+
(globalThis as any).componentExecuted.push('Parent');
312+
const show = useSignal(true);
313+
return (
314+
<main class="parent" onClick$={() => (show.value = !show.value)}>
315+
{show.value && <Component1 />}
316+
<Component1 />
317+
</main>
318+
);
319+
});
320+
const { vNode, container } = await render(<Parent />, { debug });
321+
expect((globalThis as any).componentExecuted).toEqual(['Parent', 'Component1', 'Component1']);
322+
expect(vNode).toMatchVDOM(
323+
<Component ssr-required>
324+
<main class="parent">
325+
<Component ssr-required>
326+
<div></div>
327+
</Component>
328+
<Component ssr-required>
329+
<div></div>
330+
</Component>
331+
</main>
332+
</Component>
333+
);
334+
await trigger(container.element, 'main.parent', 'click');
335+
expect((globalThis as any).componentExecuted).toEqual([
336+
'Parent',
337+
'Component1',
338+
'Component1',
339+
'Parent',
340+
]);
341+
expect(vNode).toMatchVDOM(
342+
<Component ssr-required>
343+
<main class="parent">
344+
{''}
345+
<Component ssr-required>
346+
<div></div>
347+
</Component>
348+
</main>
349+
</Component>
350+
);
351+
await trigger(container.element, 'main.parent', 'click');
352+
expect((globalThis as any).componentExecuted).toEqual([
353+
'Parent',
354+
'Component1',
355+
'Component1',
356+
'Parent',
357+
'Parent',
358+
'Component1',
359+
]);
360+
expect(vNode).toMatchVDOM(
361+
<Component ssr-required>
362+
<main class="parent">
363+
<Component ssr-required>
364+
<div></div>
365+
</Component>
366+
<Component ssr-required>
367+
<div></div>
368+
</Component>
369+
</main>
370+
</Component>
371+
);
372+
});
373+
304374
it('should remove children from component$', async () => {
305375
const log: string[] = [];
306376
const MyComp = component$((props: any) => {

0 commit comments

Comments
 (0)