Skip to content

Commit 03bfe7b

Browse files
k-g-axaviergonz
authored andcommitted
Properly destroy non-initialized nodes (fixes #1080) (#1082)
* test + fix * better test description
1 parent 4990e5a commit 03bfe7b

File tree

3 files changed

+93
-10
lines changed

3 files changed

+93
-10
lines changed

changelog.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
# 3.8.1
2+
3+
- Fixed non-initialized nodes not being destroyed [#1080](https://github.com/mobxjs/mobx-state-tree/issues/1080) through [#1082](https://github.com/mobxjs/mobx-state-tree/pull/1082) by [@k-g-a](https://github.com/k-g-a)
4+
15
# 3.8.0
26

37
- Added castToSnapshot/castToReferenceSnapshot methods for TypeScript and fixed some TypeScript typings not being properly detected when using SnapshotIn types through [#1074](https://github.com/mobxjs/mobx-state-tree/pull/1074) by [@xaviergonz](https://github.com/xaviergonz)

packages/mobx-state-tree/__tests__/core/reference.test.ts

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import {
1515
Instance,
1616
SnapshotOrInstance,
1717
isAlive,
18+
destroy,
1819
castToReferenceSnapshot
1920
} from "../../src"
2021

@@ -848,3 +849,82 @@ test("#1052 - Reference returns destroyed model after subtree replacing", () =>
848849
reactionDisposer2()
849850
}
850851
})
852+
853+
it("#1080 - does not crash trying to resolve a reference to a destroyed+recreated model", () => {
854+
const Branch = types.model("Branch", {
855+
id: types.identifierNumber,
856+
name: types.string
857+
})
858+
859+
const User = types.model("User", {
860+
id: types.identifierNumber,
861+
email: types.maybeNull(types.string),
862+
branches: types.maybeNull(types.array(Branch))
863+
})
864+
865+
const BranchStore = types
866+
.model("BranchStore", {
867+
activeBranch: types.maybeNull(types.reference(Branch))
868+
})
869+
.actions(self => ({
870+
setActiveBranch(branchId: any) {
871+
self.activeBranch = branchId
872+
}
873+
}))
874+
875+
const RootStore = types
876+
.model("RootStore", {
877+
user: types.maybeNull(User),
878+
branchStore: types.maybeNull(BranchStore)
879+
})
880+
.actions(self => ({
881+
setUser(snapshot: typeof userSnapshot) {
882+
self.user = cast(snapshot)
883+
},
884+
setBranchStore(snapshot: typeof branchStoreSnapshot) {
885+
self.branchStore = cast(snapshot)
886+
},
887+
destroyUser() {
888+
destroy(self.user!)
889+
},
890+
destroyBranchStore() {
891+
destroy(self.branchStore!)
892+
}
893+
}))
894+
895+
const userSnapshot = {
896+
id: 1,
897+
email: "test@test.com",
898+
branches: [
899+
{
900+
id: 1,
901+
name: "Branch 1"
902+
},
903+
{
904+
id: 2,
905+
name: "Branch 2"
906+
}
907+
]
908+
}
909+
910+
const branchStoreSnapshot = {}
911+
const rootStore = RootStore.create({ user: userSnapshot, branchStore: branchStoreSnapshot })
912+
913+
rootStore.branchStore!.setActiveBranch(1)
914+
expect(rootStore.branchStore!.activeBranch).toEqual({
915+
id: 1,
916+
name: "Branch 1"
917+
})
918+
919+
rootStore.destroyUser()
920+
rootStore.destroyBranchStore()
921+
922+
rootStore.setUser(userSnapshot)
923+
rootStore.setBranchStore(branchStoreSnapshot)
924+
925+
rootStore.branchStore!.setActiveBranch(2)
926+
expect(rootStore.branchStore!.activeBranch).toEqual({
927+
id: 2,
928+
name: "Branch 2"
929+
})
930+
})

packages/mobx-state-tree/src/core/node/object-node.ts

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -481,21 +481,17 @@ export class ObjectNode implements INode {
481481
@action
482482
public die() {
483483
if (this.state === NodeLifeCycle.DETACHING) return
484-
485484
if (isStateTreeNode(this.storedValue)) {
486-
// optimization: don't use walk, but getChildNodes for more efficiency
487-
walk(this.storedValue, child => {
488-
const node = getStateTreeNode(child)
489-
if (node instanceof ObjectNode) node.aboutToDie()
490-
})
491-
walk(this.storedValue, child => {
492-
const node = getStateTreeNode(child)
493-
if (node instanceof ObjectNode) node.finalizeDeath()
494-
})
485+
this.aboutToDie()
486+
this.finalizeDeath()
495487
}
496488
}
497489

498490
public aboutToDie() {
491+
this.getChildren().forEach(node => {
492+
if (node instanceof ObjectNode) node.aboutToDie()
493+
})
494+
499495
// beforeDestroy should run before the disposers since else we could end up in a situation where
500496
// a disposer added with addDisposer at this stage (beforeDestroy) is actually never released
501497
this.fireHook("beforeDestroy")
@@ -508,6 +504,9 @@ export class ObjectNode implements INode {
508504

509505
public finalizeDeath() {
510506
// invariant: not called directly but from "die"
507+
this.getChildren().forEach(node => {
508+
if (node instanceof ObjectNode) node.finalizeDeath()
509+
})
511510
this.root.identifierCache!.notifyDied(this)
512511
addReadOnlyProp(this, "snapshot", this.snapshot) // kill the computed prop and just store the last snapshot
513512

0 commit comments

Comments
 (0)