Skip to content

Commit ec9f60f

Browse files
feat(overmind): add reaction
1 parent a86f0fb commit ec9f60f

File tree

2 files changed

+175
-2
lines changed

2 files changed

+175
-2
lines changed

packages/node_modules/overmind/src/index.ts

Lines changed: 38 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import {
1010
VALUE,
1111
ITrackStateTree,
1212
IMutationTree,
13+
PATH,
1314
} from 'proxy-state-tree'
1415
import { Derived } from './derived'
1516
import { Devtools, safeValue, safeValues, DevtoolsMessage } from './Devtools'
@@ -442,8 +443,8 @@ export class Overmind<ThisConfig extends IConfiguration>
442443
this.addExecutionMutation(mutation)
443444
})
444445
}
445-
return mutationTree
446-
},
446+
return mutationTree
447+
},
447448
getTrackStateTree: () => {
448449
return this.proxyStateTree.getTrackStateTree()
449450
},
@@ -825,6 +826,41 @@ export class Overmind<ThisConfig extends IConfiguration>
825826
getMutationTree(): IMutationTree<any> {
826827
return this.proxyStateTree.getMutationTree()
827828
}
829+
reaction(
830+
stateCallback: (state: ThisConfig['state']) => any,
831+
updateCallback: (state: ThisConfig['state']) => void,
832+
deep: boolean = false
833+
) {
834+
if (deep) {
835+
const value = stateCallback(this.state)
836+
837+
if (!value || !value[IS_PROXY]) {
838+
throw new Error(
839+
'You have to return an object or array when using a "deep" reaction'
840+
)
841+
}
842+
843+
const path = value[PATH]
844+
845+
return this.addFlushListener((mutations) => {
846+
mutations.forEach((mutation) => {
847+
if (mutation.path.startsWith(path)) {
848+
updateCallback(this.state)
849+
}
850+
})
851+
})
852+
} else {
853+
const tree = this.proxyStateTree.getTrackStateTree()
854+
tree.trackScope(
855+
() => stateCallback(tree.state),
856+
() => updateCallback(tree.state)
857+
)
858+
859+
return () => {
860+
tree.dispose()
861+
}
862+
}
863+
}
828864
addMutationListener = (cb: IMutationCallback) => {
829865
return this.proxyStateTree.onMutation(cb)
830866
}
Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
import { Overmind, IAction, IState } from './'
2+
3+
type State = {
4+
foo: string
5+
upperFoo: string
6+
}
7+
8+
describe('Reaction', () => {
9+
test('should create reaction', () => {
10+
expect.assertions(2)
11+
12+
let runCount = 0
13+
14+
type State = {
15+
foo: string
16+
}
17+
const state: State = {
18+
foo: 'bar',
19+
}
20+
21+
const changeFoo: Action = ({ state }) => {
22+
state.foo = 'bar2'
23+
}
24+
25+
const config = {
26+
state,
27+
actions: {
28+
changeFoo,
29+
},
30+
}
31+
32+
type Config = typeof config
33+
interface Action<Input = void> extends IAction<Config, Input> {}
34+
35+
const app = new Overmind(config)
36+
37+
app.reaction(
38+
({ foo }) => foo,
39+
({ foo }) => {
40+
runCount++
41+
expect(foo).toBe('bar2')
42+
}
43+
)
44+
45+
app.actions.changeFoo()
46+
47+
expect(runCount).toBe(1)
48+
})
49+
test('should create deep reaction', () => {
50+
expect.assertions(2)
51+
52+
let runCount = 0
53+
54+
type State = {
55+
foo: {
56+
bar: string
57+
}
58+
}
59+
const state: State = {
60+
foo: {
61+
bar: 'baz',
62+
},
63+
}
64+
65+
const changeFoo: Action = ({ state }) => {
66+
state.foo.bar = 'baz2'
67+
}
68+
69+
const config = {
70+
state,
71+
actions: {
72+
changeFoo,
73+
},
74+
}
75+
76+
type Config = typeof config
77+
interface Action<Input = void> extends IAction<Config, Input> {}
78+
79+
const app = new Overmind(config)
80+
81+
app.reaction(
82+
({ foo }) => foo,
83+
({ foo }) => {
84+
runCount++
85+
expect(foo.bar).toBe('baz2')
86+
},
87+
true
88+
)
89+
90+
app.actions.changeFoo()
91+
92+
expect(runCount).toBe(1)
93+
})
94+
test('should throw deep reaction when invalid return object', () => {
95+
expect.assertions(1)
96+
97+
let runCount = 0
98+
99+
type State = {
100+
foo: {
101+
bar: string
102+
}
103+
}
104+
const state: State = {
105+
foo: {
106+
bar: 'baz',
107+
},
108+
}
109+
110+
const changeFoo: Action = ({ state }) => {
111+
state.foo.bar = 'baz2'
112+
}
113+
114+
const config = {
115+
state,
116+
actions: {
117+
changeFoo,
118+
},
119+
}
120+
121+
type Config = typeof config
122+
interface Action<Input = void> extends IAction<Config, Input> {}
123+
124+
const app = new Overmind(config)
125+
126+
expect(() => {
127+
app.reaction(
128+
({ foo }) => foo.bar,
129+
({ foo }) => {
130+
runCount++
131+
expect(foo.bar).toBe('baz2')
132+
},
133+
true
134+
)
135+
}).toThrow()
136+
})
137+
})

0 commit comments

Comments
 (0)