Skip to content

Commit 1440753

Browse files
committed
fix: reuse the same props instance when props are changing
1 parent 4e7f33d commit 1440753

File tree

6 files changed

+76
-10
lines changed

6 files changed

+76
-10
lines changed

.changeset/cyan-walls-sing.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: reuse the same props instance when props are changing

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

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1064,8 +1064,21 @@ export const vnode_diff = (
10641064

10651065
if (host) {
10661066
const vNodeProps = vnode_getProp<any>(host, ELEMENT_PROPS, container.$getObjectById$);
1067-
shouldRender = shouldRender || propsDiffer(jsxProps, vNodeProps);
1067+
const propsAreDifferent = propsDiffer(jsxProps, vNodeProps);
1068+
shouldRender = shouldRender || propsAreDifferent;
10681069
if (shouldRender) {
1070+
if (propsAreDifferent) {
1071+
if (vNodeProps) {
1072+
// Reuse the same props instance, qrls can use the current props instance
1073+
// as a capture ref, so we can't change it.
1074+
Object.assign(vNodeProps, jsxProps);
1075+
} else if (jsxProps) {
1076+
// if there is no props instance, create a new one.
1077+
// We can do this because we are not using the props instance for anything else.
1078+
container.setHostProp(host, ELEMENT_PROPS, jsxProps);
1079+
}
1080+
}
1081+
10691082
/**
10701083
* Mark host as not deleted. The host could have been marked as deleted if it there was a
10711084
* cleanup run. Now we found it and want to reuse it, so we need to mark it as not

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

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,6 @@ export const executeComponent = (
9898
if (!isInlineComponent) {
9999
container.setHostProp(renderHost, ELEMENT_SEQ_IDX, null);
100100
container.setHostProp(renderHost, USE_ON_LOCAL_SEQ_IDX, null);
101-
container.setHostProp(renderHost, ELEMENT_PROPS, props);
102101
}
103102

104103
if (vnode_isVNode(renderHost)) {

packages/qwik/src/core/shared/scheduler.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -587,7 +587,7 @@ export const createScheduler = (
587587
}
588588

589589
// If the host is the same (or missing), and the type is the same, we need to compare the target.
590-
if (a.$target$ !== b.$target$ || a.$payload$ !== b.$payload$) {
590+
if (a.$target$ !== b.$target$) {
591591
// 1 means that we are going to process chores as FIFO
592592
return 1;
593593
}
@@ -643,7 +643,7 @@ export const createScheduler = (
643643
* multiple times during component execution. For this reason it is necessary for us to update
644644
* the chore with the latest result of the signal.
645645
*/
646-
if (existing.$type$ === ChoreType.NODE_DIFF) {
646+
if (existing.$payload$ !== value.$payload$) {
647647
existing.$payload$ = value.$payload$;
648648
}
649649
if (existing.$executed$) {

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

Lines changed: 54 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,8 @@ function Hola(props: any) {
3939
return <div {...props}></div>;
4040
}
4141

42+
const globalObj = ['foo', 'bar'];
43+
4244
describe.each([
4345
{ render: ssrRenderToDom }, //
4446
{ render: domRender }, //
@@ -1922,14 +1924,14 @@ describe.each([
19221924
});
19231925

19241926
const Cmp = component$(() => {
1925-
const $toggled = useSignal<boolean>(false);
1927+
const toggled = useSignal<boolean>(false);
19261928

19271929
return (
19281930
<TestB
1929-
aria-label={$toggled.value ? 'a' : 'a1'}
1930-
title={$toggled.value ? 'a' : 'a1'}
1931+
aria-label={toggled.value ? 'a' : 'a1'}
1932+
title={toggled.value ? 'a' : 'a1'}
19311933
onClick$={() => {
1932-
$toggled.value = !$toggled.value;
1934+
toggled.value = !toggled.value;
19331935
}}
19341936
>
19351937
<span>Hello, World!</span>
@@ -2255,6 +2257,54 @@ describe.each([
22552257
expect(vnode_getProp(vnode_getParent(h1Element)!, OnRenderProp, null)).toBeNull();
22562258
});
22572259

2260+
it('should reuse the same props instance when props are changing', async () => {
2261+
(globalThis as any).logs = [];
2262+
type ChildProps = {
2263+
obj: string;
2264+
foo: SignalType<number>;
2265+
};
2266+
const Child = component$<ChildProps>(({ obj, foo }) => {
2267+
(globalThis as any).logs.push('child render ' + obj);
2268+
useTask$(({ track }) => {
2269+
foo && track(foo);
2270+
(globalThis as any).logs.push(obj);
2271+
});
2272+
return <></>;
2273+
});
2274+
2275+
const Cmp = component$(() => {
2276+
const foo = useSignal(0);
2277+
(globalThis as any).logs.push('parent render');
2278+
return (
2279+
<div>
2280+
<button
2281+
onClick$={() => {
2282+
foo.value === 0 ? (foo.value = 1) : (foo.value = 0);
2283+
}}
2284+
>
2285+
click
2286+
</button>
2287+
<Child obj={globalObj[foo.value]} foo={foo} />
2288+
</div>
2289+
);
2290+
});
2291+
2292+
const { document } = await render(<Cmp />, { debug });
2293+
2294+
await trigger(document.body, 'button', 'click');
2295+
2296+
expect((globalThis as any).logs).toEqual([
2297+
'parent render',
2298+
'child render foo',
2299+
'foo',
2300+
'parent render',
2301+
'bar',
2302+
'child render bar',
2303+
]);
2304+
2305+
(globalThis as any).logs = undefined;
2306+
});
2307+
22582308
describe('regression', () => {
22592309
it('#3643', async () => {
22602310
const Issue3643 = component$(() => {

starters/e2e/e2e.signals.e2e.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -450,8 +450,7 @@ test.describe("signals", () => {
450450
await expect(result).toHaveAttribute("data-value", "collision");
451451
});
452452

453-
// This test is flaky, we have it working well in v2
454-
test.skip("issue 4228", async ({ page }) => {
453+
test("issue 4228", async ({ page }) => {
455454
const buttonA = page.locator("#issue-4228-button-a");
456455
const buttonB = page.locator("#issue-4228-button-b");
457456
const buttonC = page.locator("#issue-4228-button-c");

0 commit comments

Comments
 (0)