Skip to content

Commit 74c570c

Browse files
committed
fix: memory leak for reactive attributes
1 parent b71f354 commit 74c570c

File tree

5 files changed

+424
-27
lines changed

5 files changed

+424
-27
lines changed

.changeset/tasty-glasses-bet.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: memory leak for reactive attributes

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

Lines changed: 29 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ import type { Signal } from '../reactive-primitives/signal.public';
7979
import { executeComponent } from '../shared/component-execution';
8080
import { isSlotProp } from '../shared/utils/prop';
8181
import { escapeHTML } from '../shared/utils/character-escaping';
82-
import { clearAllEffects } from '../reactive-primitives/cleanup';
82+
import { clearAllEffects, clearEffectSubscription } from '../reactive-primitives/cleanup';
8383
import { serializeAttribute } from '../shared/utils/styles';
8484
import { QError, qError } from '../shared/error/error';
8585
import { getFileLocationFromJsx } from '../shared/utils/jsx-filename';
@@ -811,6 +811,12 @@ export const vnode_diff = (
811811
return;
812812
}
813813

814+
// Clear current effect subscription if it exists
815+
const currentEffect = vnode?.[_EFFECT_BACK_REF]?.get(key);
816+
if (currentEffect) {
817+
clearEffectSubscription(container, currentEffect);
818+
}
819+
814820
if (key === 'ref') {
815821
const element = vnode.element;
816822
if (isSignal(value)) {
@@ -834,6 +840,7 @@ export const vnode_diff = (
834840
if (currentSignal === unwrappedSignal) {
835841
return;
836842
}
843+
clearAllEffects(container, vnode);
837844
value = trackSignalAndAssignHost(
838845
unwrappedSignal,
839846
vnode,
@@ -1185,7 +1192,15 @@ export const vnode_diff = (
11851192
);
11861193
let propsAreDifferent = false;
11871194
if (!shouldRender) {
1188-
propsAreDifferent = propsDiffer(jsxProps, vNodeProps);
1195+
propsAreDifferent =
1196+
propsDiffer(
1197+
(jsxProps as PropsProxy)[_CONST_PROPS],
1198+
(vNodeProps as PropsProxy)?.[_CONST_PROPS]
1199+
) ||
1200+
propsDiffer(
1201+
(jsxProps as PropsProxy)[_VAR_PROPS],
1202+
(vNodeProps as PropsProxy)?.[_VAR_PROPS]
1203+
);
11891204
shouldRender = shouldRender || propsAreDifferent;
11901205
}
11911206

@@ -1380,7 +1395,10 @@ function getComponentHash(vNode: VNode | null, getObject: (id: string) => any):
13801395
*/
13811396
function Projection() {}
13821397

1383-
function propsDiffer(src: Record<string, any>, dst: Record<string, any>): boolean {
1398+
function propsDiffer(
1399+
src: Record<string, any> | null | undefined,
1400+
dst: Record<string, any> | null | undefined
1401+
): boolean {
13841402
const srcEmpty = isPropsEmpty(src);
13851403
const dstEmpty = isPropsEmpty(dst);
13861404
if (srcEmpty && dstEmpty) {
@@ -1390,22 +1408,22 @@ function propsDiffer(src: Record<string, any>, dst: Record<string, any>): boolea
13901408
return true;
13911409
}
13921410

1393-
const srcKeys = Object.keys(src);
1394-
const dstKeys = Object.keys(dst);
1411+
const srcKeys = Object.keys(src!);
1412+
const dstKeys = Object.keys(dst!);
13951413

13961414
let srcLen = srcKeys.length;
13971415
let dstLen = dstKeys.length;
13981416

1399-
if ('children' in src) {
1417+
if ('children' in src!) {
14001418
srcLen--;
14011419
}
1402-
if (QBackRefs in src) {
1420+
if (QBackRefs in src!) {
14031421
srcLen--;
14041422
}
1405-
if ('children' in dst) {
1423+
if ('children' in dst!) {
14061424
dstLen--;
14071425
}
1408-
if (QBackRefs in dst) {
1426+
if (QBackRefs in dst!) {
14091427
dstLen--;
14101428
}
14111429

@@ -1417,15 +1435,15 @@ function propsDiffer(src: Record<string, any>, dst: Record<string, any>): boolea
14171435
if (key === 'children' || key === QBackRefs) {
14181436
continue;
14191437
}
1420-
if (!Object.prototype.hasOwnProperty.call(dst, key) || src[key] !== dst[key]) {
1438+
if (!Object.prototype.hasOwnProperty.call(dst, key) || src![key] !== dst![key]) {
14211439
return true;
14221440
}
14231441
}
14241442

14251443
return false;
14261444
}
14271445

1428-
function isPropsEmpty(props: Record<string, any>): boolean {
1446+
function isPropsEmpty(props: Record<string, any> | null | undefined): boolean {
14291447
if (!props) {
14301448
return true;
14311449
}

0 commit comments

Comments
 (0)