Skip to content

Commit 8336701

Browse files
authored
fix(standalone-context): unsub on disconnect (#53)
1 parent 3071b6c commit 8336701

File tree

4 files changed

+34
-5
lines changed

4 files changed

+34
-5
lines changed
Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
11
<template>
2-
<x-context-child-detached></x-context-child-detached>
2+
<template lwc:if={showChild}>
3+
<x-context-child-detached></x-context-child-detached>
4+
</template>
35
</template>

example/src/modules/x/contextParentDetached/contextParentDetached.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,11 @@ import parentStateFactory from 'x/parentState';
55
export default class ContextParent extends ContextfulLightningElement {
66
@api
77
parentState = parentStateFactory('parentFoo');
8+
9+
@api
10+
hideChild = false;
11+
12+
get showChild() {
13+
return !this.hideChild;
14+
}
815
}

example/src/modules/x/contextRoot/context.spec.js

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -83,13 +83,13 @@ describe('context', () => {
8383
const contextParent = querySelectorDeep('x-context-parent');
8484

8585
// Current active subscriptions
86-
// 1. <x-context-parent>
86+
// 1. <x-context-child-sibling>
8787
// 2. <x-context-child>
8888
// 3. <x-context-grand-child>
8989
expect(contextParent.parentState.subscribers.size).toBe(3);
9090

9191
// Only active subscription to parent State will be
92-
// LWC framework subscribing to component <x-context-parent>
92+
// from component: <x-context-child-sibling>
9393
// subscription of context child and grand child
9494
// should be removed after unsubscribing
9595
contextParent.hideChild = true;
@@ -125,4 +125,16 @@ describe('context', () => {
125125
const childWithDetachedFromContext = querySelectorDeep('.child-content-detached', el);
126126
expect(childWithDetachedFromContext.innerText).to.include('parentFoo');
127127
});
128+
129+
it('standalone context is unsubscribed once the child disconnects', async () => {
130+
const el = await clientSideRender(parentEl, componentPath, {});
131+
const contextParentDetached = querySelectorDeep('x-context-parent-detached', el);
132+
133+
expect(contextParentDetached.parentState.subscribers.size).toBe(1);
134+
135+
contextParentDetached.hideChild = true;
136+
await freshRender();
137+
138+
expect(contextParentDetached.parentState.subscribers.size).toBe(0);
139+
});
128140
});

packages/@lwc/state/src/standalone-context.ts

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { connectContext } from './shared.js';
1+
import { connectContext, disconnectContext } from './shared.js';
22
import { SignalBaseClass, type Signal } from '@lwc/signals';
33
import type { ExposedUpdater } from './types.js';
44
import type { ContextRuntimeAdapter } from './runtime-interface.js';
@@ -13,7 +13,6 @@ class ConsumedContextSignal<StateShape extends ValidStateShape>
1313
{
1414
private desiredStateDef: ValidStateDef<StateShape>;
1515
private _value: StateShape | null = null;
16-
// Currently unused. Should be called once `disconnectContext` is implemented.
1716
private unsubscribe: () => void = () => {};
1817

1918
constructor(stateDef: ValidStateDef<StateShape>) {
@@ -44,6 +43,15 @@ class ConsumedContextSignal<StateShape extends ValidStateShape>
4443
},
4544
);
4645
}
46+
47+
[disconnectContext](_componentId: ContextRuntimeAdapter<object>['component']) {
48+
// Unlike the state manager's fromContext which can subscribe to multiple
49+
// ancestor contexts simultaneously, this standalone version only subscribes
50+
// to a single context at a time. Therefore, we don't need to use componentId
51+
// to track subscriptions.
52+
this.unsubscribe();
53+
this.unsubscribe = () => {};
54+
}
4755
}
4856

4957
export const fromContext = <

0 commit comments

Comments
 (0)