Skip to content

Commit 233e581

Browse files
committed
feat(cursors): handle changing component props
1 parent 7128b3f commit 233e581

File tree

3 files changed

+55
-62
lines changed

3 files changed

+55
-62
lines changed

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

Lines changed: 47 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -1378,7 +1378,6 @@ function handleProps(
13781378
container: ClientContainer
13791379
): boolean {
13801380
let shouldRender = false;
1381-
let propsAreDifferent = false;
13821381
if (vNodeProps) {
13831382
const effects = vNodeProps[_PROPS_HANDLER].$effects$;
13841383
const constPropsDifferent = handleChangedProps(
@@ -1388,33 +1387,24 @@ function handleProps(
13881387
container,
13891388
false
13901389
);
1391-
propsAreDifferent = constPropsDifferent;
13921390
shouldRender ||= constPropsDifferent;
13931391
if (effects && effects.size > 0) {
1394-
const varPropsDifferent = handleChangedProps(
1392+
handleChangedProps(
13951393
jsxProps[_VAR_PROPS],
13961394
vNodeProps[_VAR_PROPS],
13971395
vNodeProps[_PROPS_HANDLER],
1398-
container
1396+
container,
1397+
true
13991398
);
1400-
1401-
propsAreDifferent ||= varPropsDifferent;
14021399
// don't mark as should render, effects will take care of it
1403-
// shouldRender ||= varPropsDifferent;
1404-
}
1405-
}
1406-
1407-
if (propsAreDifferent) {
1408-
if (vNodeProps) {
1409-
// Reuse the same props instance, qrls can use the current props instance
1410-
// as a capture ref, so we can't change it.
1411-
vNodeProps[_OWNER] = (jsxProps as PropsProxy)[_OWNER];
1412-
} else if (jsxProps) {
1413-
// If there is no props instance, create a new one.
1414-
// We can do this because we are not using the props instance for anything else.
1415-
vnode_setProp(host as VirtualVNode, ELEMENT_PROPS, jsxProps);
1416-
vNodeProps = jsxProps;
14171400
}
1401+
// Update the owner after all props have been synced
1402+
vNodeProps[_OWNER] = (jsxProps as PropsProxy)[_OWNER];
1403+
} else if (jsxProps) {
1404+
// If there is no props instance, create a new one.
1405+
// We can do this because we are not using the props instance for anything else.
1406+
vnode_setProp(host as VirtualVNode, ELEMENT_PROPS, jsxProps);
1407+
vNodeProps = jsxProps;
14181408
}
14191409
return shouldRender;
14201410
}
@@ -1426,51 +1416,49 @@ function handleChangedProps(
14261416
container: ClientContainer,
14271417
triggerEffects: boolean = true
14281418
): boolean {
1429-
const srcEmpty = isPropsEmpty(src);
1430-
const dstEmpty = isPropsEmpty(dst);
1431-
1432-
if (srcEmpty && dstEmpty) {
1419+
if (isPropsEmpty(src) && isPropsEmpty(dst)) {
14331420
return false;
14341421
}
14351422

1436-
if (srcEmpty || dstEmpty) {
1437-
return true;
1438-
}
1439-
1440-
const srcKeys = Object.keys(src!);
1441-
const dstKeys = Object.keys(dst!);
1442-
1443-
let srcLen = srcKeys.length;
1444-
let dstLen = dstKeys.length;
1445-
if ('children' in src!) {
1446-
srcLen--;
1447-
}
1448-
if (QBackRefs in src!) {
1449-
srcLen--;
1450-
}
1451-
if ('children' in dst!) {
1452-
dstLen--;
1453-
}
1454-
if (QBackRefs in dst!) {
1455-
dstLen--;
1456-
}
1423+
propsHandler.$container$ = container;
1424+
let changed = false;
14571425

1458-
if (srcLen !== dstLen) {
1459-
return true;
1426+
// Update changed/added props from src
1427+
if (src) {
1428+
for (const key in src) {
1429+
if (key === 'children' || key === QBackRefs) {
1430+
continue;
1431+
}
1432+
if (!dst || src[key] !== dst[key]) {
1433+
changed = true;
1434+
if (triggerEffects) {
1435+
if (dst) {
1436+
// Update the value in dst BEFORE triggering effects
1437+
// so effects see the new value
1438+
// Note: Value is not triggering effects, because we are modyfing direct VAR_PROPS object
1439+
dst[key] = src[key];
1440+
}
1441+
triggerPropsProxyEffect(propsHandler, key);
1442+
} else {
1443+
// Early return for const props (no effects)
1444+
return true;
1445+
}
1446+
}
1447+
}
14601448
}
14611449

1462-
let changed = false;
1463-
propsHandler.$container$ = container;
1464-
for (const key of srcKeys) {
1465-
if (key === 'children' || key === QBackRefs) {
1466-
continue;
1467-
}
1468-
if (!Object.prototype.hasOwnProperty.call(dst, key) || src![key] !== dst![key]) {
1469-
changed = true;
1470-
if (triggerEffects) {
1471-
triggerPropsProxyEffect(propsHandler, key);
1472-
} else {
1473-
return true;
1450+
// Remove props that are in dst but not in src
1451+
if (dst) {
1452+
for (const key in dst) {
1453+
if (key === 'children' || key === QBackRefs) {
1454+
continue;
1455+
}
1456+
if (!src || !(key in src)) {
1457+
changed = true;
1458+
if (triggerEffects) {
1459+
delete dst[key];
1460+
triggerPropsProxyEffect(propsHandler, key);
1461+
}
14741462
}
14751463
}
14761464
}

packages/qwik/src/core/shared/cursor/cursor-walker.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -240,5 +240,6 @@ function getNextVNode(vNode: VNode): VNode | null {
240240
}
241241
// all array items checked, children are no longer dirty
242242
parent!.dirty &= ~ChoreBits.CHILDREN;
243+
parent!.dirtyChildren = null;
243244
return getNextVNode(parent!);
244245
}

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

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ import { ErrorProvider } from '../../testing/rendering.unit-util';
3030
import * as qError from '../shared/error/error';
3131
import { QContainerValue } from '../shared/types';
3232
import { ELEMENT_PROPS, OnRenderProp, QContainerAttr } from '../shared/utils/markers';
33-
import { vnode_locate } from '../client/vnode';
33+
import { vnode_getProp, vnode_locate } from '../client/vnode';
3434
import type { PropsProxy } from '../shared/jsx/props-proxy';
3535
import { _PROPS_HANDLER } from '../shared/utils/constants';
3636

@@ -2332,7 +2332,7 @@ describe.each([
23322332
);
23332333
const h1Element = vnode_locate(container.rootVNode, document.querySelector('h1')!);
23342334

2335-
expect(h1Element.parent!.getProp(OnRenderProp, null)).toBeNull();
2335+
expect(vnode_getProp(h1Element.parent!, OnRenderProp, null)).toBeNull();
23362336
});
23372337

23382338
it('should reuse the same props instance when props are changing', async () => {
@@ -2741,7 +2741,11 @@ describe.each([
27412741
</Component>
27422742
);
27432743

2744-
const propsProxy = vNode!.getProp<PropsProxy>(ELEMENT_PROPS, container.$getObjectById$)!;
2744+
const propsProxy = vnode_getProp<PropsProxy>(
2745+
vNode!,
2746+
ELEMENT_PROPS,
2747+
container.$getObjectById$ as ((id: string) => PropsProxy) | null
2748+
)!;
27452749
expect(propsProxy[_PROPS_HANDLER]?.$effects$).toBeDefined();
27462750
expect(propsProxy[_PROPS_HANDLER]?.$effects$?.size).toBe(1);
27472751

0 commit comments

Comments
 (0)