Skip to content

Commit f0351d3

Browse files
committed
fix(): do not re-render controlled node after disposed
1 parent b356ef0 commit f0351d3

File tree

4 files changed

+36
-14
lines changed

4 files changed

+36
-14
lines changed

packages/runtime/src/internal/Renderer.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -610,7 +610,10 @@ async function legacyRenderBrick(
610610
});
611611

612612
// Ignore stale renders
613-
if (renderId === currentRenderId) {
613+
if (
614+
renderId === currentRenderId &&
615+
!(returnNode.tag !== RenderTag.ROOT && returnNode.disposed)
616+
) {
614617
if (onUnmount) {
615618
listenerFactory(
616619
onUnmount,
@@ -1004,6 +1007,11 @@ async function legacyRenderBrick(
10041007
({ failed, output: incrementalOutput } = result);
10051008
}
10061009

1010+
// istanbul ignore next: covered by e2e tests
1011+
if (brick.disposed) {
1012+
return true;
1013+
}
1014+
10071015
newControlNode.child = incrementalOutput.node;
10081016
mergeRenderOutput(newOutput, {
10091017
...incrementalOutput,

packages/runtime/src/internal/RendererContext.ts

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -363,6 +363,21 @@ export class RendererContext {
363363
#unmountAbstracts(abstracts: Set<RenderAbstract>) {
364364
for (const item of abstracts) {
365365
item.disposes?.forEach((dispose) => dispose());
366+
delete item.disposes;
367+
}
368+
}
369+
370+
#cleanUpNodes(nodes: Set<RenderAbstract | RenderBrick>) {
371+
for (const node of nodes) {
372+
delete node.child;
373+
delete node.sibling;
374+
delete (node as Partial<RenderChildNode>).return;
375+
node.disposed = true;
376+
if (node.tag === RenderTag.BRICK) {
377+
delete node.element;
378+
delete (node as Partial<RenderBrick>).runtimeContext;
379+
}
380+
Object.freeze(node);
366381
}
367382
}
368383

@@ -387,6 +402,10 @@ export class RendererContext {
387402
node: RenderChildNode,
388403
oldNode: RenderChildNode
389404
) {
405+
// istanbul ignore next: defensive check
406+
if (returnNode.tag !== RenderTag.ROOT && returnNode.disposed) {
407+
return;
408+
}
390409
const [prevLastNormal, prevLastPortal] = findLastChildNodes(oldNode);
391410
const insertBeforeChild =
392411
(prevLastNormal
@@ -452,10 +471,8 @@ export class RendererContext {
452471
}
453472
node.sibling = oldNode.sibling;
454473

455-
// Clean up old node
456-
delete oldNode.child;
457-
delete oldNode.sibling;
458-
delete (oldNode as Partial<RenderChildNode>).return;
474+
this.#cleanUpNodes(removeAbstracts);
475+
this.#cleanUpNodes(removeBricks);
459476

460477
// Resume `return`
461478
current = node;

packages/runtime/src/internal/interfaces.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ export interface RenderAbstract extends BaseRenderNode {
6868
return: RenderReturnNode;
6969
iid?: string;
7070
disposes?: (() => void)[];
71+
disposed?: boolean;
7172
}
7273

7374
export interface BaseRenderNode {
@@ -94,6 +95,7 @@ export interface RuntimeBrick {
9495
portal?: boolean;
9596
ref?: string;
9697
disposes?: (() => void)[];
98+
disposed?: boolean;
9799
}
98100

99101
export type MetaInfoOfEventListener = [

packages/runtime/src/themeAndMode.spec.ts

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -167,20 +167,15 @@ describe("variant", () => {
167167
setThemeVariant("elevo");
168168
expect(getThemeVariant()).toEqual("elevo");
169169
expect(document.documentElement.dataset.variant).toBe("elevo");
170-
});
171170

172-
test("should apply the current theme variant", () => {
173-
expect(getThemeVariant()).toEqual("default");
174-
expect(document.documentElement.dataset.variant).toBe("default");
171+
// Set the same variant again
175172
setThemeVariant("elevo");
176-
expect(document.documentElement.dataset.variant).toBe("elevo");
173+
expect(getThemeVariant()).toEqual("elevo");
177174
});
178175

179-
test("should apply the specified theme variant", () => {
176+
test("should fallback to default variant if an invalid one is set", () => {
177+
setThemeVariant("oops" as any);
180178
expect(getThemeVariant()).toEqual("default");
181179
expect(document.documentElement.dataset.variant).toBe("default");
182-
setThemeVariant("elevo");
183-
expect(getThemeVariant()).toEqual("elevo");
184-
expect(document.documentElement.dataset.variant).toBe("elevo");
185180
});
186181
});

0 commit comments

Comments
 (0)