Skip to content

Commit 62a34c8

Browse files
committed
Rips out the AsyncComponentProvider in prep for more flexibility.
1 parent c6cb55f commit 62a34c8

File tree

6 files changed

+95
-92
lines changed

6 files changed

+95
-92
lines changed

src/AsyncComponentProvider.js

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,21 +3,30 @@
33
import React from 'react'
44
import type { ExecContext, ProviderChildContext } from './types'
55

6+
import createContext from './createContext'
7+
68
type Props = {
79
// eslint-disable-next-line
810
children?: any,
9-
execContext: ExecContext,
11+
execContext?: ExecContext,
1012
};
1113

1214
class AsyncComponentProvider extends React.Component {
1315
props: Props
16+
execContext: ExecContext
17+
18+
constructor(props : Props, context : Object) {
19+
super(props, context)
20+
21+
this.execContext = props.execContext || createContext()
22+
}
1423

1524
getChildContext() : ProviderChildContext {
1625
return {
1726
asyncComponents: {
18-
getNextId: this.props.execContext.getNextId,
19-
getComponent: this.props.execContext.getComponent,
20-
registerComponent: this.props.execContext.registerComponent,
27+
getNextId: this.execContext.getNextId,
28+
getComponent: this.execContext.getComponent,
29+
registerComponent: this.execContext.registerComponent,
2130
},
2231
}
2332
}
@@ -33,7 +42,11 @@ AsyncComponentProvider.propTypes = {
3342
getNextId: React.PropTypes.func.isRequired,
3443
getComponent: React.PropTypes.func.isRequired,
3544
registerComponent: React.PropTypes.func.isRequired,
36-
}).isRequired,
45+
}),
46+
}
47+
48+
AsyncComponentProvider.defaultProps = {
49+
execContext: undefined,
3750
}
3851

3952
AsyncComponentProvider.childContextTypes = {

src/__tests__/__snapshots__/integration.test.js.snap

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ exports[`integration tests render server and client 3`] = `
1818
Object {
1919
"getComponent": [Function],
2020
"getNextId": [Function],
21-
"getResolved": [Function],
21+
"getState": [Function],
2222
"registerComponent": [Function],
2323
}
2424
}
@@ -60,7 +60,7 @@ exports[`integration tests render server and client 4`] = `
6060
Object {
6161
"getComponent": [Function],
6262
"getNextId": [Function],
63-
"getResolved": [Function],
63+
"getState": [Function],
6464
"registerComponent": [Function],
6565
}
6666
}

src/__tests__/integration.test.js

Lines changed: 42 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,13 @@ import React from 'react'
44
import { renderToStaticMarkup } from 'react-dom/server'
55
import { mount } from 'enzyme'
66

7-
import { createAsyncComponent, withAsyncComponents } from '../'
8-
import { STATE_IDENTIFIER } from '../constants'
7+
import {
8+
AsyncComponentProvider,
9+
createContext,
10+
createAsyncComponent,
11+
withAsyncComponents,
12+
STATE_IDENTIFIER,
13+
} from '../'
914

1015
function Bob({ children }) {
1116
return (<div>{children}</div>)
@@ -40,23 +45,25 @@ const BoundaryAsyncBob = createAsyncComponent({
4045
name: 'BoundaryAsyncBob',
4146
})
4247

43-
const app = (
44-
<AsyncBob>
45-
<div>
46-
<AsyncBobTwo>
47-
<span>In Render.</span>
48-
</AsyncBobTwo>
49-
<DeferredAsyncBob>
50-
<span>In Defer.</span>
51-
</DeferredAsyncBob>
52-
<BoundaryAsyncBob>
53-
<span>In Boundary but outside an AsyncComponent, server render me!</span>
54-
<AsyncBobThree>
55-
<span>In Boundary - Do not server render me!</span>
56-
</AsyncBobThree>
57-
</BoundaryAsyncBob>
58-
</div>
59-
</AsyncBob>
48+
const app = execContext => (
49+
<AsyncComponentProvider execContext={execContext}>
50+
<AsyncBob>
51+
<div>
52+
<AsyncBobTwo>
53+
<span>In Render.</span>
54+
</AsyncBobTwo>
55+
<DeferredAsyncBob>
56+
<span>In Defer.</span>
57+
</DeferredAsyncBob>
58+
<BoundaryAsyncBob>
59+
<span>In Boundary but outside an AsyncComponent, server render me!</span>
60+
<AsyncBobThree>
61+
<span>In Boundary - Do not server render me!</span>
62+
</AsyncBobThree>
63+
</BoundaryAsyncBob>
64+
</div>
65+
</AsyncBob>
66+
</AsyncComponentProvider>
6067
)
6168

6269
describe('integration tests', () => {
@@ -70,23 +77,27 @@ describe('integration tests', () => {
7077
delete global.window
7178

7279
// "Server" side render...
73-
return withAsyncComponents(app)
74-
.then(({ appWithAsyncComponents, state, STATE_IDENTIFIER: STATE_ID }) => {
75-
const serverString = renderToStaticMarkup(appWithAsyncComponents)
80+
const serverContext = createContext()
81+
const serverApp = app(serverContext)
82+
return withAsyncComponents(serverApp)
83+
.then(() => {
84+
const serverString = renderToStaticMarkup(serverApp)
7685
expect(serverString).toMatchSnapshot()
77-
expect(state).toMatchSnapshot()
86+
expect(serverContext.getState()).toMatchSnapshot()
7887
// Restore the window and attach the state to the "window" for the client
7988
global.window = windowTemp
80-
global.window[STATE_ID] = state
89+
global.window[STATE_IDENTIFIER] = serverContext.getState()
8190
return serverString
8291
})
83-
.then(serverHTML =>
92+
.then((serverHTML) => {
8493
// "Client" side render...
85-
withAsyncComponents(app)
86-
.then(({ appWithAsyncComponents }) => {
87-
const clientRenderWrapper = mount(appWithAsyncComponents)
94+
const clientContext = createContext()
95+
const clientApp = app(clientContext)
96+
return withAsyncComponents(clientApp)
97+
.then(() => {
98+
const clientRenderWrapper = mount(clientApp)
8899
expect(clientRenderWrapper).toMatchSnapshot()
89-
expect(renderToStaticMarkup(appWithAsyncComponents)).toEqual(serverHTML)
100+
expect(renderToStaticMarkup(clientApp)).toEqual(serverHTML)
90101
return clientRenderWrapper
91102
})
92103
// Now give the client side components time to resolve
@@ -96,8 +107,8 @@ describe('integration tests', () => {
96107
// Now a full render should have occured on client
97108
.then(clientRenderWrapper =>
98109
expect(clientRenderWrapper).toMatchSnapshot(),
99-
),
100-
)
110+
)
111+
})
101112
})
102113

103114
it('a component only gets resolved once', () => {

src/createContext.js

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
export default function createExecContext() {
2+
let idPointer = 0
3+
const registry = {}
4+
return {
5+
getNextId: () => {
6+
idPointer += 1
7+
return idPointer
8+
},
9+
registerComponent(id, Component) {
10+
registry[id] = Component
11+
},
12+
getComponent(id) {
13+
return registry[id]
14+
},
15+
getState() {
16+
return {
17+
resolved: Object.keys(registry).reduce(
18+
(acc, cur) => Object.assign(acc, { [cur]: true }),
19+
{},
20+
),
21+
}
22+
},
23+
}
24+
}

src/index.js

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,15 @@
11
/* @flow */
22

3+
import AsyncComponentProvider from './AsyncComponentProvider'
4+
import createContext from './createContext'
35
import createAsyncComponent from './createAsyncComponent'
46
import withAsyncComponents from './withAsyncComponents'
5-
6-
// create context
7-
// pass context into createAsyncComponentFactory
7+
import { STATE_IDENTIFIER } from './constants'
88

99
export {
10+
AsyncComponentProvider,
11+
createContext,
1012
createAsyncComponent,
1113
withAsyncComponents,
14+
STATE_IDENTIFIER,
1215
}

src/withAsyncComponents.js

Lines changed: 3 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -1,59 +1,18 @@
11
/* @flow */
22

3-
import React from 'react'
43
import reactTreeWalker from 'react-tree-walker'
5-
import AsyncComponentProvider from './AsyncComponentProvider'
64
import { STATE_IDENTIFIER } from './constants'
75
import type { React$Element } from './types'
86

9-
function createExecContext() {
10-
let idPointer = 0
11-
const registry = {}
12-
return {
13-
getNextId: () => {
14-
idPointer += 1
15-
return idPointer
16-
},
17-
registerComponent(id, Component) {
18-
registry[id] = Component
19-
},
20-
getComponent(id) {
21-
return registry[id]
22-
},
23-
getResolved() {
24-
return Object.keys(registry).reduce(
25-
(acc, cur) => Object.assign(acc, { [cur]: true }),
26-
{},
27-
)
28-
},
29-
}
30-
}
31-
32-
type Result = {
33-
appWithAsyncComponents : React$Element,
34-
state? : { resolved: Array<number> },
35-
STATE_IDENTIFIER? : string,
36-
}
37-
38-
export default function withAsyncComponents(app : React$Element) : Promise<Result> {
39-
const execContext = createExecContext()
40-
7+
export default function withAsyncComponents(app : React$Element) : Promise<any> {
418
const isBrowser = typeof window !== 'undefined'
429
const rehydrateState = isBrowser
4310
&& typeof window[STATE_IDENTIFIER] !== 'undefined'
4411
? window[STATE_IDENTIFIER]
4512
: null
4613

47-
const appWithAsyncComponents = (
48-
<AsyncComponentProvider execContext={execContext}>
49-
{app}
50-
</AsyncComponentProvider>
51-
)
52-
5314
if (isBrowser && !rehydrateState) {
54-
return Promise.resolve({
55-
appWithAsyncComponents,
56-
})
15+
return Promise.resolve()
5716
}
5817

5918
const visitor = (element, instance, context) => {
@@ -77,14 +36,7 @@ export default function withAsyncComponents(app : React$Element) : Promise<Resul
7736
return true
7837
}
7938

80-
return reactTreeWalker(appWithAsyncComponents, visitor, {})
39+
return reactTreeWalker(app, visitor, {})
8140
// Swallow errors.
8241
.catch(() => undefined)
83-
// Ensure that state rehydration is killed
84-
.then(() => { if (typeof window === 'object') { window[STATE_IDENTIFIER] = null } })
85-
.then(() => ({
86-
appWithAsyncComponents,
87-
state: { resolved: execContext.getResolved() },
88-
STATE_IDENTIFIER,
89-
}))
9042
}

0 commit comments

Comments
 (0)