Skip to content

Commit 39c0288

Browse files
fix(overmind): make derived track with current tree
1 parent dab67f1 commit 39c0288

File tree

9 files changed

+116
-59
lines changed

9 files changed

+116
-59
lines changed

packages/node_modules/overmind-devtools-client/src/components/Inspector/index.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -242,7 +242,7 @@ const Nested: FunctionComponent<NestedProps> = memo(
242242
{Array.isArray(value)
243243
? value.map((_, index) =>
244244
renderValue({
245-
path: path.concat('.' + String(index)),
245+
path: path.concat((path ? '.' : '') + String(index)),
246246
value: value[index],
247247
renderPaths,
248248
expandedPaths,
@@ -254,7 +254,7 @@ const Nested: FunctionComponent<NestedProps> = memo(
254254
)
255255
: Object.keys(value).map((key) =>
256256
renderValue({
257-
path: path.concat('.' + key),
257+
path: path.concat((path ? '.' : '') + key),
258258
value: value[key],
259259
renderPaths,
260260
expandedPaths,

packages/node_modules/overmind-devtools-client/src/overmind/state.ts

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ type State = {
2929
currentTab: Tab
3030
showApps: boolean
3131
currentAction: Derive<State, Action>
32-
currentApp: App
32+
currentApp: Derive<State, App>
3333
componentsMounted: Derive<State, Component[]>
3434
componentsUpdateCount: Derive<State, number>
3535
componentsStatePathCount: Derive<State, number>
@@ -52,9 +52,7 @@ const state: State = {
5252
apps: {},
5353
newPortValue: '',
5454
currentTab: Tab.State,
55-
get currentApp() {
56-
const state: State = this
57-
55+
currentApp: (state) => {
5856
return state.apps[state.currentAppName]
5957
},
6058
componentsMounted: (state) =>

packages/node_modules/overmind/src/derived.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ describe('Derived', () => {
2929
expect(app.state.upperFoo).toEqual('BAR')
3030
})
3131

32-
test('should track derived state', () => {
32+
test.only('should track derived state', () => {
3333
let renderCount = 0
3434
const changeFoo: Action = ({ state }) => {
3535
state.foo = 'bar2'

packages/node_modules/overmind/src/derived.ts

Lines changed: 71 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -5,93 +5,115 @@ import {
55
IS_PROXY,
66
ITrackStateTree,
77
TrackStateTree,
8+
IMutationTree,
9+
MutationTree,
810
} from 'proxy-state-tree'
911

1012
import { EventType, Events } from './internalTypes'
1113

1214
export class Derived {
1315
private isDirty: boolean = true
1416
private trackStateTree: ITrackStateTree<any>
15-
private scope: any
17+
private previousTreeAccess: any
1618
private value: any
1719
private paths: Set<string>
1820
private updateCount: number = 0
1921
private disposeOnMutation: () => void
2022
constructor(private cb: (state: object, parent: object) => void) {
2123
const boundEvaluate: any = this.evaluate.bind(this)
2224

23-
boundEvaluate.dispose = () => {
24-
this.disposeOnMutation()
25-
this.trackStateTree.dispose()
25+
if (process.env.NODE_ENV === 'development') {
26+
boundEvaluate.dispose = () => {
27+
this.disposeOnMutation()
28+
this.trackStateTree.dispose()
29+
}
2630
}
2731

2832
return boundEvaluate
2933
}
34+
private createScope(tree, path) {
35+
const pathAsArray = path.split('.')
36+
pathAsArray.pop()
37+
const parent = pathAsArray.reduce((curr, key) => curr[key], tree.state)
38+
39+
return () => this.cb(parent, tree.state)
40+
}
3041
evaluate(
3142
eventHub: EventEmitter<Events>,
43+
tree: ITrackStateTree<any> | IMutationTree<any>,
3244
proxyStateTree: ProxyStateTree<any>,
3345
path
3446
) {
3547
if (!this.trackStateTree) {
3648
this.trackStateTree = proxyStateTree.getTrackStateTree()
49+
this.trackStateTree.shouldTrack = true
50+
this.trackStateTree.callback = () => {
51+
this.isDirty = true
52+
}
3753

38-
const pathAsArray = path.split('.')
39-
pathAsArray.pop()
40-
const parent = pathAsArray.reduce(
41-
(curr, key) => curr[key],
42-
this.trackStateTree.state
43-
)
44-
45-
this.scope = () => this.cb(parent, this.trackStateTree.state)
46-
this.disposeOnMutation = proxyStateTree.onMutation(
47-
(_, paths, flushId) => {
48-
if (this.isDirty) {
49-
return
50-
}
51-
52-
for (let mutationPath of paths) {
53-
if (this.paths.has(mutationPath)) {
54-
this.isDirty = true
55-
eventHub.emitAsync(EventType.DERIVED_DIRTY, {
56-
derivedPath: path,
57-
path: mutationPath,
58-
flushId,
59-
})
54+
if (process.env.NODE_ENV === 'development') {
55+
this.disposeOnMutation = proxyStateTree.onMutation(
56+
(_, paths, flushId) => {
57+
if (this.isDirty) {
6058
return
6159
}
60+
61+
for (let mutationPath of paths) {
62+
if (this.paths.has(mutationPath)) {
63+
this.isDirty = true
64+
eventHub.emitAsync(EventType.DERIVED_DIRTY, {
65+
derivedPath: path,
66+
path: mutationPath,
67+
flushId,
68+
})
69+
return
70+
}
71+
}
6272
}
63-
}
64-
)
73+
)
74+
}
6575
}
6676

67-
if (
68-
this.isDirty ||
69-
(this.value &&
70-
this.value[IS_PROXY] &&
71-
this.value[VALUE][(proxyStateTree as any).PROXY] !== this.value)
72-
) {
73-
this.value = this.trackStateTree.trackScope(this.scope)
77+
let isDirty
7478

75-
this.isDirty = false
76-
this.paths = new Set(this.trackStateTree.pathDependencies)
77-
eventHub.emitAsync(EventType.DERIVED, {
78-
path,
79-
paths: Array.from(this.paths),
80-
updateCount: this.updateCount,
81-
value: this.value,
82-
})
83-
this.updateCount++
79+
// During development we need to re-evaluate the derived so that
80+
// it tracks on the mutation tree passed in, in case of mutations
81+
// of the returned derived
82+
if (process.env.NODE_ENV === 'development') {
83+
isDirty =
84+
this.isDirty ||
85+
(tree instanceof MutationTree && this.previousTreeAccess !== tree)
86+
} else {
87+
isDirty = this.isDirty
8488
}
8589

86-
// Tracks the paths for the consumer of this derived value
87-
for (let path of this.paths) {
88-
const currentTree = proxyStateTree.currentTree
90+
if (isDirty) {
91+
const getPaths = tree.trackPaths()
92+
93+
this.value = this.createScope(tree, path)()
94+
this.isDirty = false
95+
this.paths = getPaths()
96+
97+
// We add the paths to the derived
98+
this.trackStateTree.pathDependencies.clear()
99+
100+
for (let path of this.paths) {
101+
this.trackStateTree.addTrackingPath(path)
102+
}
89103

90-
if (currentTree instanceof TrackStateTree) {
91-
currentTree.addTrackingPath(path)
104+
if (process.env.NODE_ENV === 'development') {
105+
eventHub.emitAsync(EventType.DERIVED, {
106+
path,
107+
paths: Array.from(this.paths),
108+
updateCount: this.updateCount,
109+
value: this.value,
110+
})
111+
this.updateCount++
92112
}
93113
}
94114

115+
this.previousTreeAccess = tree
116+
95117
return this.value
96118
}
97119
}

packages/node_modules/overmind/src/index.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ import {
88
ProxyStateTree,
99
TTree,
1010
VALUE,
11+
ITrackStateTree,
12+
IMutationTree,
1113
} from 'proxy-state-tree'
1214
import { Derived } from './derived'
1315
import { Devtools, safeValue, safeValues, DevtoolsMessage } from './Devtools'
@@ -376,7 +378,8 @@ export class Overmind<ThisConfig extends IConfiguration>
376378
this.getState(configuration) as any,
377379
{
378380
devmode,
379-
dynamicWrapper: (_, path, func) => func(eventHub, proxyStateTree, path),
381+
dynamicWrapper: (tree, path, func) =>
382+
func(eventHub, tree, proxyStateTree, path),
380383
onGetter: devmode
381384
? (path, value) => {
382385
this.eventHub.emitAsync(EventType.GETTER, {
@@ -808,10 +811,10 @@ export class Overmind<ThisConfig extends IConfiguration>
808811
})
809812
}, {}) as any
810813
}
811-
getTrackStateTree() {
814+
getTrackStateTree(): ITrackStateTree<any> {
812815
return this.proxyStateTree.getTrackStateTree()
813816
}
814-
getMutationTree() {
817+
getMutationTree(): IMutationTree<any> {
815818
return this.proxyStateTree.getMutationTree()
816819
}
817820
addMutationListener = (cb: IMutationCallback) => {

packages/node_modules/proxy-state-tree/src/MutationTree.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,23 @@ export class MutationTree<T extends object> implements IMutationTree<T> {
1515
mutations: IMutation[] = []
1616
objectChanges = new Set<string>()
1717
isTracking: boolean = false
18+
onPathAccess: (path: string) => void
1819
constructor(master: IProxyStateTree<T>, proxifier?: IProxifier<T>) {
1920
this.isTracking = true
2021
this.master = master
2122
this.proxifier = proxifier || new Proxifier(this)
2223
this.state = this.proxifier.proxify(master.sourceState, '')
2324
}
25+
trackPaths() {
26+
const paths = new Set<string>()
27+
this.onPathAccess = (path) => {
28+
paths.add(path)
29+
}
30+
return () => {
31+
this.onPathAccess = null
32+
return paths
33+
}
34+
}
2435
getMutations() {
2536
const mutations = this.mutations.slice()
2637

packages/node_modules/proxy-state-tree/src/Proxyfier.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,8 @@ export class Proxifier {
113113

114114
trackingTree && trackingTree.proxifier.trackPath(nestedPath)
115115

116+
proxifier.tree.onPathAccess && proxifier.tree.onPathAccess(nestedPath)
117+
116118
const method = String(prop)
117119

118120
if (arrayMutations.has(method)) {
@@ -204,6 +206,8 @@ export class Proxifier {
204206

205207
trackingTree && trackingTree.proxifier.trackPath(nestedPath)
206208

209+
proxifier.tree.onPathAccess && proxifier.tree.onPathAccess(nestedPath)
210+
207211
if (typeof targetValue === 'function') {
208212
const dynamicValue = proxifier.tree.master.options.dynamicWrapper
209213
? proxifier.tree.master.options.dynamicWrapper(

packages/node_modules/proxy-state-tree/src/TrackStateTree.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,24 @@ export class TrackStateTree<T extends object> implements ITrackStateTree<T> {
1313
shouldTrack: boolean = false
1414
state: T
1515
proxifier: IProxifier<T>
16+
onPathAccess: (path: string) => void
1617
constructor(master: IProxyStateTree<T>) {
1718
this.master = master
1819
this.proxifier = master.proxifier
1920
this.state = master.state
2021
}
22+
trackPaths() {
23+
const paths = new Set<string>()
24+
25+
this.onPathAccess = (path) => {
26+
paths.add(path)
27+
}
28+
29+
return () => {
30+
this.onPathAccess = null
31+
return paths
32+
}
33+
}
2134
canMutate() {
2235
return false
2336
}
@@ -38,6 +51,7 @@ export class TrackStateTree<T extends object> implements ITrackStateTree<T> {
3851
track(cb?: ITrackCallback) {
3952
this.master.changeTrackStateTree(this)
4053
this.shouldTrack = true
54+
4155
if (this.callback) {
4256
for (let path of this.pathDependencies) {
4357
this.master.removePathDependency(path, this.callback)

packages/node_modules/proxy-state-tree/src/types.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ export interface IMutationTree<T extends object> {
2121
canMutate(): boolean
2222
getMutations(): IMutation[]
2323
getObjectChanges(): Set<string>
24+
trackPaths(): () => Set<string>
2425
flush(
2526
async?: boolean
2627
): {
@@ -33,6 +34,7 @@ export interface IMutationTree<T extends object> {
3334
proxifier: IProxifier<T>
3435
mutations: IMutation[]
3536
state: T
37+
onPathAccess: (path: string) => void
3638
}
3739

3840
export interface ITrackCallback {
@@ -53,14 +55,17 @@ export interface ITrackStateTree<T extends object> {
5355
track(cb?: ITrackCallback): ITrackStateTree<T>
5456
stopTracking(): void
5557
trackScope(scope: ITrackScopedCallback<T>, callback?: ITrackCallback): any
58+
trackPaths(): () => Set<string>
5659
canTrack(): boolean
5760
canMutate(): boolean
5861
dispose(): ITrackStateTree<T>
5962
master: IProxyStateTree<T>
63+
shouldTrack: boolean
6064
proxifier: IProxifier<T>
6165
state: T
6266
pathDependencies: Set<string>
6367
callback: ITrackCallback
68+
onPathAccess: (path: string) => void
6469
}
6570

6671
export interface IOptions {

0 commit comments

Comments
 (0)