Skip to content

Commit fe1d3f4

Browse files
authored
feat: Explicit Resource Management support in reactions (#4558)
1 parent 8b54ab1 commit fe1d3f4

File tree

4 files changed

+82
-0
lines changed

4 files changed

+82
-0
lines changed

.changeset/tiny-rice-walk.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"mobx": minor
3+
---
4+
5+
Add Explicit Resource Management support in reactions

docs/reactions.md

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -329,6 +329,54 @@ class OrderLine {
329329

330330
</details>
331331

332+
In environments that support [Explicit Resource Management](https://github.com/tc39/proposal-explicit-resource-management),
333+
the disposer function includes a `[Symbol.dispose]` method that can be used to
334+
dispose of the reaction. This can be useful when disposing of several reactions
335+
simultaneously or when disposing of reactions alongside other Disposables.
336+
337+
<details id="disposable-stack"><summary>**Example:** using DisposableStack<a href="#disposable-stack" class="tip-anchor"></a></summary>
338+
339+
```javascript
340+
function createSomeDisposableResource() {}
341+
342+
class Vat {
343+
value = 1.2
344+
345+
constructor() {
346+
makeAutoObservable(this)
347+
}
348+
}
349+
350+
const vat = new Vat()
351+
352+
class OrderLine {
353+
price = 10
354+
amount = 1
355+
disposableStack = new DisposableStack()
356+
someDisposableResource
357+
358+
constructor() {
359+
makeAutoObservable(this)
360+
361+
this.disposableStack.use(autorun(() => {
362+
doSomethingWith(this.price * this.amount)
363+
}))
364+
365+
this.disposableStack.use(autorun(() => {
366+
doSomethingWith(this.price * this.amount * vat.value)
367+
}))
368+
369+
this.someDisposableResource = this.disposableStack.use(createSomeDisposableResource())
370+
}
371+
372+
[Symbol.dispose]() {
373+
this.disposableStack[Symbol.dispose]();
374+
}
375+
}
376+
```
377+
378+
</details>
379+
332380
## Use reactions sparingly!
333381

334382
As it was already said, you won't create reactions very often.

packages/mobx/__tests__/v5/base/reaction.js

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
*/
44
const mobx = require("../../../src/mobx.ts")
55
const reaction = mobx.reaction
6+
const $mobx = mobx.$mobx
67
const utils = require("../../v5/utils/test-utils")
78

89
test("basic", () => {
@@ -704,3 +705,27 @@ describe("reaction opts requiresObservable", () => {
704705
expect(messages.length).toBe(0)
705706
})
706707
})
708+
709+
describe("explicit resource management", () => {
710+
Symbol.dispose ??= Symbol("Symbol.dispose")
711+
712+
test("reaction has [Symbol.dispose] and calling it disposes of reaction", () => {
713+
const a = mobx.observable.box(1)
714+
const values = []
715+
716+
const disposeReaction = reaction(
717+
() => a.get(),
718+
newValue => {
719+
values.push(newValue)
720+
}
721+
)
722+
723+
expect(disposeReaction[Symbol.dispose]).toBeInstanceOf(Function)
724+
725+
disposeReaction[Symbol.dispose]()
726+
a.set(2)
727+
728+
expect(values).toEqual([])
729+
expect(disposeReaction[$mobx].isDisposed).toBe(true)
730+
})
731+
})

packages/mobx/src/core/reaction.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -240,6 +240,10 @@ export class Reaction implements IDerivation, IReactionPublic {
240240
abortSignal?.addEventListener?.("abort", dispose)
241241
dispose[$mobx] = this
242242

243+
if ("dispose" in Symbol && typeof Symbol.dispose === "symbol") {
244+
dispose[Symbol.dispose] = dispose
245+
}
246+
243247
return dispose
244248
}
245249

0 commit comments

Comments
 (0)