Skip to content

Commit dcb0d02

Browse files
feat(overmind-react): provider for HOC
1 parent 148dc06 commit dcb0d02

File tree

6 files changed

+210
-32
lines changed

6 files changed

+210
-32
lines changed

packages/node_modules/overmind-react/src/__snapshots__/index.test.tsx.snap

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,12 @@ exports[`React should allow using mocked Overmind 1`] = `
2424
</h1>
2525
`;
2626

27+
exports[`React should be able to use Provider with connect 1`] = `
28+
<h1>
29+
bar
30+
</h1>
31+
`;
32+
2733
exports[`React should connect actions and state to class components 1`] = `
2834
<h1>
2935
bar

packages/node_modules/overmind-react/src/index.test.tsx

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -286,4 +286,54 @@ describe('React', () => {
286286
expect(renderCount).toBe(1)
287287
expect(tree).toMatchSnapshot()
288288
})
289+
test('should be able to use Provider with connect', () => {
290+
let renderCount = 0
291+
292+
const doThis: Action = ({ state }) => {
293+
state.foo = 'bar2'
294+
}
295+
const config = {
296+
state: {
297+
foo: 'bar',
298+
},
299+
actions: {
300+
doThis,
301+
},
302+
}
303+
304+
type IConfig = {
305+
state: {
306+
foo: typeof config.state.foo
307+
}
308+
actions: {
309+
doThis: typeof doThis
310+
}
311+
}
312+
313+
interface Action<Input = void> extends IAction<IConfig, Input> {}
314+
315+
interface Connect extends IConnect<IConfig> {}
316+
317+
const connect = createConnect()
318+
319+
const FooComponent: React.FunctionComponent<Connect> = ({ overmind }) => {
320+
renderCount++
321+
322+
return <h1>{overmind.state.foo}</h1>
323+
}
324+
325+
const ConnectedFooComponent = connect(FooComponent)
326+
327+
const mock = createOvermindMock(config)
328+
const tree = renderer
329+
.create(
330+
<Provider value={mock}>
331+
<ConnectedFooComponent />
332+
</Provider>
333+
)
334+
.toJSON()
335+
336+
expect(renderCount).toBe(1)
337+
expect(tree).toMatchSnapshot()
338+
})
289339
})

packages/node_modules/overmind-react/src/index.ts

Lines changed: 38 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@ import {
2222
createContext,
2323
useContext,
2424
} from 'react'
25-
import { IMutation } from 'proxy-state-tree'
2625

2726
const IS_PRODUCTION = process.env.NODE_ENV === 'production'
2827

@@ -40,10 +39,10 @@ type Omit<T, K extends keyof T> = Pick<
4039

4140
export interface IConnect<Config extends IConfiguration> {
4241
overmind: {
43-
state: IContext<Config>['state']
44-
actions: IContext<Config>['actions']
45-
effects: IContext<Config>['effects']
46-
addMutationListener: (cb: (mutation: IMutation) => void) => () => void
42+
state: Overmind<Config>['state']
43+
actions: Overmind<Config>['actions']
44+
effects: Overmind<Config>['effects']
45+
addMutationListener: Overmind<Config>['addMutationListener']
4746
}
4847
}
4948

@@ -59,10 +58,10 @@ export const Provider: React.ProviderExoticComponent<
5958
export const createHook = <Config extends IConfiguration>(
6059
overmindInstance?: Overmind<Config>
6160
): (() => {
62-
state: IContext<Config>['state']
63-
actions: IContext<Config>['actions']
64-
effects: IContext<Config>['effects']
65-
addMutationListener: (cb: (mutation: IMutation) => void) => () => void
61+
state: Overmind<Config>['state']
62+
actions: Overmind<Config>['actions']
63+
effects: Overmind<Config>['effects']
64+
addMutationListener: Overmind<Config>['addMutationListener']
6665
}) => {
6766
let currentComponentInstanceId = 0
6867
const {
@@ -156,7 +155,7 @@ export const createHook = <Config extends IConfiguration>(
156155
}
157156

158157
export const createConnect = <A extends Overmind<IConfiguration>>(
159-
overmind: A
158+
overmindInstance?: A
160159
) => {
161160
return <Props>(
162161
component: IReactComponent<
@@ -189,33 +188,37 @@ export const createConnect = <A extends Overmind<IConfiguration>>(
189188

190189
if (IS_PRODUCTION) {
191190
class HOC extends Component {
192-
tree = (overmind as any).proxyStateTree.getTrackStateTree()
191+
tree: any
192+
overmind: any
193193
state: {
194194
overmind: any
195195
}
196-
constructor(props) {
196+
static contextType = context
197+
constructor(props, context) {
197198
super(props)
199+
this.overmind = overmindInstance || context
200+
this.tree = this.overmind.proxyStateTree.getTrackStateTree()
198201
this.state = {
199202
overmind: {
200203
state: this.tree.state,
201-
effects: overmind.effects,
202-
actions: overmind.actions,
203-
addMutationListener: overmind.addMutationListener,
204+
effects: this.overmind.effects,
205+
actions: this.overmind.actions,
206+
addMutationListener: this.overmind.addMutationListener,
204207
onUpdate: this.onUpdate,
205208
tree: this.tree,
206209
},
207210
}
208211
}
209212
componentWillUnmount() {
210-
;(overmind as any).proxyStateTree.disposeTree(this.tree)
213+
this.overmind.proxyStateTree.disposeTree(this.tree)
211214
}
212215
onUpdate = () => {
213216
this.setState({
214217
overmind: {
215218
state: this.tree.state,
216-
effects: overmind.effects,
217-
actions: overmind.actions,
218-
addMutationListener: overmind.addMutationListener,
219+
effects: this.overmind.effects,
220+
actions: this.overmind.actions,
221+
addMutationListener: this.overmind.addMutationListener,
219222
onUpdate: this.onUpdate,
220223
tree: this.tree,
221224
},
@@ -246,35 +249,39 @@ export const createConnect = <A extends Overmind<IConfiguration>>(
246249
return HOC as any
247250
} else {
248251
class HOC extends Component {
249-
tree = (overmind as any).proxyStateTree.getTrackStateTree()
252+
tree: any
253+
overmind: any
250254
componentInstanceId = componentInstanceId++
251255
currentFlushId = 0
252256
state: {
253257
overmind: any
254258
}
255-
constructor(props) {
259+
static contextType = context
260+
constructor(props, context) {
256261
super(props)
262+
this.overmind = overmindInstance || context
263+
this.tree = this.overmind.proxyStateTree.getTrackStateTree()
257264
this.state = {
258265
overmind: {
259266
state: this.tree.state,
260-
effects: overmind.effects,
261-
actions: overmind.actions,
262-
addMutationListener: overmind.addMutationListener,
267+
effects: this.overmind.effects,
268+
actions: this.overmind.actions,
269+
addMutationListener: this.overmind.addMutationListener,
263270
onUpdate: this.onUpdate,
264271
tree: this.tree,
265272
},
266273
}
267274
}
268275
componentDidMount() {
269-
overmind.eventHub.emitAsync(EventType.COMPONENT_ADD, {
276+
this.overmind.eventHub.emitAsync(EventType.COMPONENT_ADD, {
270277
componentId: populatedComponent.__componentId,
271278
componentInstanceId: this.componentInstanceId,
272279
name,
273280
paths: Array.from(this.tree.pathDependencies) as any,
274281
})
275282
}
276283
componentDidUpdate() {
277-
overmind.eventHub.emitAsync(EventType.COMPONENT_UPDATE, {
284+
this.overmind.eventHub.emitAsync(EventType.COMPONENT_UPDATE, {
278285
componentId: populatedComponent.__componentId,
279286
componentInstanceId: this.componentInstanceId,
280287
name,
@@ -283,8 +290,8 @@ export const createConnect = <A extends Overmind<IConfiguration>>(
283290
})
284291
}
285292
componentWillUnmount() {
286-
;(overmind as any).proxyStateTree.disposeTree(this.tree)
287-
overmind.eventHub.emitAsync(EventType.COMPONENT_REMOVE, {
293+
this.overmind.proxyStateTree.disposeTree(this.tree)
294+
this.overmind.eventHub.emitAsync(EventType.COMPONENT_REMOVE, {
288295
componentId: populatedComponent.__componentId,
289296
componentInstanceId: this.componentInstanceId,
290297
name,
@@ -295,9 +302,9 @@ export const createConnect = <A extends Overmind<IConfiguration>>(
295302
this.setState({
296303
overmind: {
297304
state: this.tree.state,
298-
effects: overmind.effects,
299-
actions: overmind.actions,
300-
addMutationListener: overmind.addMutationListener,
305+
effects: this.overmind.effects,
306+
actions: this.overmind.actions,
307+
addMutationListener: this.overmind.addMutationListener,
301308
onUpdate: this.onUpdate,
302309
tree: this.tree,
303310
},

packages/node_modules/overmind/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,7 @@ export function createOvermindMock<Config extends IConfiguration>(
133133
state: mock.state,
134134
onInitialize: (mock as any).onInitialize,
135135
proxyStateTree: (mock as any).proxyStateTree,
136+
eventHub: (mock as any).eventHub,
136137
} as any
137138
}
138139

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
export default (ts) =>
2+
ts
3+
? [
4+
{
5+
fileName: 'overmind/index.ts',
6+
code: `
7+
import { IConfig } from 'overmind'
8+
import { createConnect } from 'overmind-react'
9+
import { state } from './state'
10+
import * as actions from './actions'
11+
12+
export const config = {
13+
state,
14+
actions
15+
}
16+
17+
declare module 'overmind' {
18+
interface Config extends IConfig<typeof config> {}
19+
}
20+
21+
export interface Connect extends IConnect<typeof config> {}
22+
23+
export const connect = createConnect()
24+
`,
25+
},
26+
{
27+
fileName: 'components/index.tsx',
28+
code: `
29+
import * as React from 'react'
30+
import { render } from 'react-dom'
31+
import { Overmind } from 'overmind'
32+
import { Provider } from 'overmind-react'
33+
import { config } from './overmind'
34+
import App from './components/App'
35+
36+
const overmind = new Overmind(config)
37+
38+
render((
39+
<Provider value={overmind}>
40+
<App />
41+
</Provider>
42+
), document.querySelector('#app'))
43+
`,
44+
},
45+
{
46+
fileName: 'components/App.tsx',
47+
code: `
48+
import * as React from 'react'
49+
import { connect } from '../overmind'
50+
51+
const App: React.FunctionComponent = ({ overmind }) => {
52+
return <div />
53+
}
54+
55+
export default connect(App)
56+
`,
57+
},
58+
]
59+
: [
60+
{
61+
fileName: 'overmind/index.js',
62+
code: `
63+
import { Overmind } from 'overmind'
64+
import { createConnect } from 'overmind-react'
65+
66+
export const config = {
67+
state: {},
68+
actions: {}
69+
}
70+
71+
export const connect = createConnect()
72+
`,
73+
},
74+
{
75+
fileName: 'components/index.tsx',
76+
code: `
77+
import React from 'react'
78+
import { render } from 'react-dom'
79+
import { Overmind } from 'overmind'
80+
import { Provider } from 'overmind-react'
81+
import { config } from './overmind'
82+
import App from './components/App'
83+
84+
const overmind = new Overmind(config)
85+
86+
render((
87+
<Provider value={overmind}>
88+
<App />
89+
</Provider>
90+
), document.querySelector('#app'))
91+
`,
92+
},
93+
{
94+
fileName: 'components/App.jsx',
95+
code: `
96+
import React from 'react'
97+
import { connect } from '../overmind'
98+
99+
const App = ({ overmind }) => {
100+
return <div />
101+
}
102+
103+
export default connect(App)
104+
`,
105+
},
106+
]

packages/overmind-website/guides/beginner/06_usingovermindwithreact.md

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ h(Example, { name: "guide/usingovermindwithreact/hook_effect_subscription" })
3737

3838
### Provider
3939

40-
With the hooks API you can also expose the Overmind instance with a Provider. This detaches the overmind instance from the actual **useOvermind** hook. It is rather consumed from the React context. This makes it easier to test components and you can use component libraries which is built for Overmind.
40+
You can also expose the Overmind instance with a Provider. This detaches the overmind instance from the actual **useOvermind** hook. It is rather consumed from the React context. This makes it easier to test components and do server side rendering.
4141

4242
```marksy
4343
h(Example, { name: "guide/usingovermindwithreact/hook_provider" })
@@ -74,4 +74,12 @@ To run effects in components based on changes to state you use the **addMutation
7474

7575
```marksy
7676
h(Example, { name: "guide/usingovermindwithreact/hoc_effect" })
77+
```
78+
79+
### Provider
80+
81+
You can also expose the Overmind instance with a Provider. This detaches the overmind instance from the actual **connect** hook. It is rather consumed from the React context. This makes it easier to test components and do server side rendering.
82+
83+
```marksy
84+
h(Example, { name: "guide/usingovermindwithreact/hoc_provider" })
7785
```

0 commit comments

Comments
 (0)