Skip to content

Commit 29a360e

Browse files
authored
feat: Upgrade React Context API (#216)
1 parent db7ea3d commit 29a360e

File tree

10 files changed

+64
-83
lines changed

10 files changed

+64
-83
lines changed

README.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -155,7 +155,7 @@ The first argument can be a function that returns a promise, a promise itself, o
155155
- `key`: `'foo'` || `module => module.foo` -- *default: `default` export in ES6 and `module.exports` in ES5*
156156
- `timeout`: `15000` -- *default*
157157
- `onError`: `(error, { isServer }) => handleError(error, isServer)
158-
- `onLoad`: `(module, { isSync, isServer }, props, context) => do(module, isSync, isServer, props, context)`
158+
- `onLoad`: `(module, { isSync, isServer }, props) => do(module, isSync, isServer, props)`
159159
- `minDelay`: `0` -- *default*
160160
- `alwaysDelay`: `false` -- *default*
161161
- `loadingTransition`: `true` -- *default*
@@ -187,11 +187,11 @@ The first argument can be a function that returns a promise, a promise itself, o
187187

188188
- `onLoad` is a callback function that receives the *entire* module. It allows you to export and put to use things other than your `default` component export, like reducers, sagas, etc. E.g:
189189
```js
190-
onLoad: (module, info, props, context) => {
191-
context.store.replaceReducer({ ...otherReducers, foo: module.fooReducer })
190+
onLoad: (module, info, props) => {
191+
props.store.replaceReducer({ ...otherReducers, foo: module.fooReducer })
192192

193193
// if a route triggered component change, new reducers needs to reflect it
194-
context.store.dispatch({ type: 'INIT_ACTION_FOR_ROUTE', payload: { param: props.param } })
194+
props.store.dispatch({ type: 'INIT_ACTION_FOR_ROUTE', payload: { param: props.param } })
195195
}
196196
````
197197
**As you can see we have thought of everything you might need to really do code-splitting right (we have real apps that use this stuff).** `onLoad` is fired directly before the component is rendered so you can setup any reducers/etc it depends on. Unlike the `onAfter` prop, this *option* to the `universal` *HOC* is only fired the first time the module is received. *Also note*: it will fire on the server, so do `if (!isServer)` if you have to. But also keep in mind you will need to do things like replace reducers on both the server + client for the imported component that uses new reducers to render identically in both places.

__tests__/index.js

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -280,8 +280,7 @@ describe('other options', () => {
280280
await waitFor(50)
281281
const info = { isServer: false, isSync: false }
282282
const props = { foo: 'bar' }
283-
const context = {}
284-
expect(onLoad).toBeCalledWith(mod, info, props, context)
283+
expect(onLoad).toBeCalledWith(mod, info, props)
285284

286285
expect(component.toJSON()).toMatchSnapshot() // success
287286
})
@@ -299,8 +298,7 @@ describe('other options', () => {
299298

300299
await waitFor(50)
301300
const info = { isServer: false, isSync: false }
302-
const context = {}
303-
expect(onLoad).toBeCalledWith(mod, info, {}, context)
301+
expect(onLoad).toBeCalledWith(mod, info, {})
304302

305303
expect(component.toJSON()).toMatchSnapshot() // success
306304
})
@@ -322,7 +320,6 @@ describe('other options', () => {
322320
isServer: false,
323321
isSync: true
324322
},
325-
{},
326323
{}
327324
)
328325
})

__tests__/requireUniversalModule.js

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -361,11 +361,10 @@ describe('other options', () => {
361361
})
362362

363363
const props = { foo: 'bar' }
364-
const context = {}
365-
await requireAsync(props, context)
364+
await requireAsync(props)
366365

367366
const info = { isServer: false, isSync: false }
368-
expect(onLoad).toBeCalledWith(mod, info, props, context)
367+
expect(onLoad).toBeCalledWith(mod, info, props)
369368
expect(onLoad).not.toBeCalledWith('foo', info, props)
370369
})
371370

@@ -386,11 +385,10 @@ describe('other options', () => {
386385
})
387386

388387
const props = { foo: 'bar' }
389-
const context = {}
390-
requireSync(props, context)
388+
requireSync(props)
391389

392390
const info = { isServer: false, isSync: true }
393-
expect(onLoad).toBeCalledWith(mod, info, props, context)
391+
expect(onLoad).toBeCalledWith(mod, info, props)
394392
expect(onLoad).not.toBeCalledWith('foo', info, props)
395393

396394
delete global.__webpack_require__

__tests__/utils.js

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -57,13 +57,12 @@ test('resolveExport: finds export and calls onLoad', () => {
5757
const onLoad = jest.fn()
5858
const mod = { foo: 'bar' }
5959
const props = { baz: 123 }
60-
const context = {}
6160

62-
const exp = resolveExport(mod, 'foo', onLoad, undefined, props, context)
61+
const exp = resolveExport(mod, 'foo', onLoad, undefined, props)
6362
expect(exp).toEqual('bar')
6463

6564
const info = { isServer: false, isSync: false }
66-
expect(onLoad).toBeCalledWith(mod, info, props, context)
65+
expect(onLoad).toBeCalledWith(mod, info, props)
6766
// todo: test caching
6867
})
6968

src/context.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import React from 'react'
2+
3+
const ReportContext = React.createContext({ report: () => {} })
4+
5+
export default ReportContext

src/flowTypes.js

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -73,13 +73,12 @@ export type Key = string | null | ((module: ?(Object | Function)) => any)
7373
export type OnLoad = (
7474
module: ?(Object | Function),
7575
info: { isServer: boolean },
76-
props: Object,
77-
context: Object
76+
props: Object
7877
) => void
7978
export type OnError = (error: Object, info: { isServer: boolean }) => void
8079

81-
export type RequireAsync = (props: Object, context: Object) => Promise<?any>
82-
export type RequireSync = (props: Object, context: Object) => ?any
80+
export type RequireAsync = (props: Object) => Promise<?any>
81+
export type RequireSync = (props: Object) => ?any
8382
export type AddModule = (props: Object) => ?string
8483
export type Mod = Object | Function
8584
export type Tools = {
@@ -108,6 +107,10 @@ export type Props = {
108107
onError?: OnErrorProp
109108
}
110109

110+
export type Context = {
111+
report?: (chunkName: string) => void
112+
}
113+
111114
export type GenericComponent<Props> = Props =>
112115
| React$Element<any>
113116
| Class<React.Component<{}, Props, mixed>>

src/index.js

Lines changed: 25 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,10 @@ import type {
99
ComponentOptions,
1010
RequireAsync,
1111
State,
12-
Props
12+
Props,
13+
Context
1314
} from './flowTypes'
15+
import ReportContext from './context'
1416

1517
import {
1618
DefaultLoading,
@@ -63,16 +65,17 @@ export default function universal<Props: Props>(
6365

6466
state: State
6567
props: Props
66-
context: Object
6768
/* eslint-enable react/sort-comp */
6869

69-
static preload(props: Props, context: Object = {}) {
70+
static contextType = ReportContext
71+
72+
static preload(props: Props) {
7073
props = props || {}
7174
const { requireAsync, requireSync } = req(asyncModule, options, props)
7275
let mod
7376

7477
try {
75-
mod = requireSync(props, context)
78+
mod = requireSync(props)
7679
}
7780
catch (error) {
7881
return Promise.reject(error)
@@ -81,7 +84,7 @@ export default function universal<Props: Props>(
8184
return Promise.resolve()
8285
.then(() => {
8386
if (mod) return mod
84-
return requireAsync(props, context)
87+
return requireAsync(props)
8588
})
8689
.then(mod => {
8790
hoist(UniversalComponent, mod, {
@@ -92,11 +95,11 @@ export default function universal<Props: Props>(
9295
})
9396
}
9497

95-
static preloadWeak(props: Props, context: Object = {}) {
98+
static preloadWeak(props: Props) {
9699
props = props || {}
97100
const { requireSync } = req(asyncModule, options, props)
98101

99-
const mod = requireSync(props, context)
102+
const mod = requireSync(props)
100103
if (mod) {
101104
hoist(UniversalComponent, mod, {
102105
preload: true,
@@ -107,16 +110,10 @@ export default function universal<Props: Props>(
107110
return mod
108111
}
109112

110-
static contextTypes = {
111-
store: PropTypes.object,
112-
report: PropTypes.func
113-
}
114-
115113
requireAsyncInner(
116114
requireAsync: RequireAsync,
117115
props: Props,
118116
state: State,
119-
context: Object = {},
120117
isMount?: boolean
121118
) {
122119
if (!state.mod && loadingTransition) {
@@ -125,9 +122,9 @@ export default function universal<Props: Props>(
125122

126123
const time = new Date()
127124

128-
requireAsync(props, context)
125+
requireAsync(props)
129126
.then((mod: ?any) => {
130-
const state = { mod, props, context }
127+
const state = { mod, props }
131128

132129
const timeLapsed = new Date() - time
133130
if (timeLapsed < minDelay) {
@@ -137,7 +134,7 @@ export default function universal<Props: Props>(
137134

138135
this.update(state, isMount)
139136
})
140-
.catch(error => this.update({ error, props, context }))
137+
.catch(error => this.update({ error, props }))
141138
}
142139

143140
update = (
@@ -191,7 +188,7 @@ export default function universal<Props: Props>(
191188
this.setState(state)
192189
}
193190
// $FlowFixMe
194-
init(props, context) {
191+
init(props) {
195192
const { addModule, requireSync, requireAsync, asyncOnly } = req(
196193
asyncModule,
197194
options,
@@ -201,24 +198,23 @@ export default function universal<Props: Props>(
201198
let mod
202199

203200
try {
204-
mod = requireSync(props, context)
201+
mod = requireSync(props)
205202
}
206203
catch (error) {
207-
return __update(props, { error, props, context }, this._initialized)
204+
return __update(props, { error, props }, this._initialized)
208205
}
209206

210207
this._asyncOnly = asyncOnly
211208
const chunkName = addModule(props) // record the module for SSR flushing :)
212-
213-
if (context.report) {
214-
context.report(chunkName)
209+
if (this.context && this.context.report) {
210+
this.context.report(chunkName)
215211
}
216212

217213
if (mod || isServer) {
218214
this.handleBefore(true, true, isServer)
219215
return __update(
220216
props,
221-
{ asyncOnly, props, mod, context },
217+
{ asyncOnly, props, mod },
222218
this._initialized,
223219
true,
224220
true,
@@ -230,16 +226,15 @@ export default function universal<Props: Props>(
230226
this.requireAsyncInner(
231227
requireAsync,
232228
props,
233-
{ props, asyncOnly, mod, context },
234-
context,
229+
{ props, asyncOnly, mod },
235230
true
236231
)
237-
return { mod, asyncOnly, context, props }
232+
return { mod, asyncOnly, props }
238233
}
239234

240-
constructor(props: Props, context: {}) {
235+
constructor(props: Props, context: Context) {
241236
super(props, context)
242-
this.state = this.init(this.props, this.context)
237+
this.state = this.init(this.props)
243238
// $FlowFixMe
244239
this.state.error = null
245240
}
@@ -252,7 +247,7 @@ export default function universal<Props: Props>(
252247
currentState.props
253248
)
254249
if (isHMR() && shouldUpdate(currentState.props, nextProps)) {
255-
const mod = requireSync(nextProps, currentState.context)
250+
const mod = requireSync(nextProps)
256251
return { ...currentState, mod }
257252
}
258253
return null
@@ -275,7 +270,7 @@ export default function universal<Props: Props>(
275270
let mod
276271

277272
try {
278-
mod = requireSync(this.props, this.context)
273+
mod = requireSync(this.props)
279274
}
280275
catch (error) {
281276
return this.update({ error })

src/report-chunks.js

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import React from 'react'
44
import PropTypes from 'prop-types'
5+
import ReportContext from './context'
56

67
type Props = {
78
report: Function,
@@ -13,17 +14,18 @@ export default class ReportChunks extends React.Component<void, Props, *> {
1314
report: PropTypes.func.isRequired
1415
}
1516

16-
static childContextTypes = {
17-
report: PropTypes.func.isRequired
18-
}
19-
20-
getChildContext() {
21-
return {
22-
report: this.props.report
17+
constructor(props: Props) {
18+
super(props)
19+
this.state = {
20+
report: props.report
2321
}
2422
}
2523

2624
render() {
27-
return React.Children.only(this.props.children)
25+
return (
26+
<ReportContext.Provider value={this.state}>
27+
{this.props.children}
28+
</ReportContext.Provider>
29+
)
2830
}
2931
}

src/requireUniversalModule.js

Lines changed: 4 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ export default function requireUniversalModule<Props: Props>(
4848
const { chunkName, path, resolve, load } = config
4949
const asyncOnly = (!path && !resolve) || typeof chunkName === 'function'
5050

51-
const requireSync = (props: Object, context: Object): ?any => {
51+
const requireSync = (props: Object): ?any => {
5252
let exp = loadFromCache(chunkName, props, modCache)
5353

5454
if (!exp) {
@@ -67,23 +67,14 @@ export default function requireUniversalModule<Props: Props>(
6767
}
6868

6969
if (mod) {
70-
exp = resolveExport(
71-
mod,
72-
key,
73-
onLoad,
74-
chunkName,
75-
props,
76-
context,
77-
modCache,
78-
true
79-
)
70+
exp = resolveExport(mod, key, onLoad, chunkName, props, modCache, true)
8071
}
8172
}
8273

8374
return exp
8475
}
8576

86-
const requireAsync = (props: Object, context: Object): Promise<?any> => {
77+
const requireAsync = (props: Object): Promise<?any> => {
8778
const exp = loadFromCache(chunkName, props, modCache)
8879
if (exp) return Promise.resolve(exp)
8980

@@ -108,15 +99,7 @@ export default function requireUniversalModule<Props: Props>(
10899
const resolve = mod => {
109100
clearTimeout(timer)
110101

111-
const exp = resolveExport(
112-
mod,
113-
key,
114-
onLoad,
115-
chunkName,
116-
props,
117-
context,
118-
modCache
119-
)
102+
const exp = resolveExport(mod, key, onLoad, chunkName, props, modCache)
120103
if (exp) return res(exp)
121104

122105
reject(new Error('export not found'))

0 commit comments

Comments
 (0)