Skip to content

Commit f3b9ae9

Browse files
committed
Fixes an issue where component identifiers were not being cycled which caused different instances of AsyncComponent to share a resolved Component.
1 parent 01fe31f commit f3b9ae9

8 files changed

+59
-41
lines changed

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@ const AsyncProduct = createAsyncComponent({
104104
// 🖕 Webpack's code splitting API
105105
});
106106

107-
export default AsynProduct;
107+
export default AsyncProduct;
108108
```
109109

110110
Now, you can simply import `AsyncProduct` anywhere in your application and not have to worry about having to call `createAsyncComponent` again.
@@ -328,7 +328,7 @@ Understand your own applications needs and use the options appropriately . I per
328328

329329
At the moment there is one known caveat in using this library: it doesn't support React Hot Loader (RHL). You can still use Webpack's standard Hot Module Replacement, however, RHL does not respond nicely to the architecture of `react-async-component`.
330330

331-
TODO: I'll post up some details why and perhaps we could work to find a solution.
331+
To be clear, using React Hot Loader will not throw errors, your components just won't do any hot reloading. I am investigating possible solutions and will report back here if and when I discover any workarounds.
332332

333333
## FAQs
334334

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "react-async-component",
3-
"version": "0.2.0",
3+
"version": "0.2.1",
44
"description": "Create Components that resolve asynchronously, with support for server side rendering and code splitting.",
55
"license": "MIT",
66
"main": "commonjs/index.js",

src/AsyncComponentProvider.js

Lines changed: 9 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -4,28 +4,20 @@ import React from 'react';
44
import type { ExecContext, ProviderChildContext } from './types';
55

66
type Props = {
7+
// eslint-disable-next-line
78
children?: any,
89
execContext: ExecContext,
910
};
1011

1112
class AsyncComponentProvider extends React.Component {
12-
id: number;
13-
registry: { [key:number] : Function };
14-
15-
constructor(props : Props) {
16-
super(props);
17-
this.id = 0;
18-
this.registry = {};
19-
}
13+
props: Props
2014

2115
getChildContext() : ProviderChildContext {
2216
return {
2317
asyncComponents: {
24-
nextId: () => {
25-
this.id += 1;
26-
return this.id;
27-
},
28-
getComponent: id => this.props.execContext.getComponent(id),
18+
getNextId: this.props.execContext.getNextId,
19+
getComponent: this.props.execContext.getComponent,
20+
registerComponent: this.props.execContext.registerComponent,
2921
},
3022
};
3123
}
@@ -38,14 +30,17 @@ class AsyncComponentProvider extends React.Component {
3830
AsyncComponentProvider.propTypes = {
3931
children: React.PropTypes.node.isRequired,
4032
execContext: React.PropTypes.shape({
33+
getNextId: React.PropTypes.func.isRequired,
4134
getComponent: React.PropTypes.func.isRequired,
35+
registerComponent: React.PropTypes.func.isRequired,
4236
}).isRequired,
4337
};
4438

4539
AsyncComponentProvider.childContextTypes = {
4640
asyncComponents: React.PropTypes.shape({
47-
nextId: React.PropTypes.func.isRequired,
41+
getNextId: React.PropTypes.func.isRequired,
4842
getComponent: React.PropTypes.func.isRequired,
43+
registerComponent: React.PropTypes.func.isRequired,
4944
}).isRequired,
5045
};
5146

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ exports[`integration works 1`] = `
33
execContext={
44
Object {
55
"getComponent": [Function],
6+
"getNextId": [Function],
67
"getResolved": [Function],
78
"registerComponent": [Function],
89
}
@@ -45,6 +46,7 @@ exports[`integration works 3`] = `
4546
execContext={
4647
Object {
4748
"getComponent": [Function],
49+
"getNextId": [Function],
4850
"getResolved": [Function],
4951
"registerComponent": [Function],
5052
}
@@ -85,6 +87,7 @@ exports[`integration works 4`] = `
8587
execContext={
8688
Object {
8789
"getComponent": [Function],
90+
"getNextId": [Function],
8891
"getResolved": [Function],
8992
"registerComponent": [Function],
9093
}

src/__tests__/createAsyncComponent.test.js

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,14 @@ import { mount } from 'enzyme';
55
import createAsyncComponent from '../createAsyncComponent';
66

77
describe('createAsyncComponent', () => {
8+
const contextStub = {
9+
asyncComponents: {
10+
getNextId: () => 1,
11+
getComponent: () => undefined,
12+
registerComponent: () => undefined,
13+
},
14+
};
15+
816
it('should handle unmounting ensuring that resolved promises do not call setState', () => {
917
const resolveDelay = 10;
1018
const Bob = createAsyncComponent({
@@ -16,7 +24,7 @@ describe('createAsyncComponent', () => {
1624
),
1725
});
1826
const setStateSpy = sinon.spy(Bob.prototype, 'setState');
19-
const renderWrapper = mount(<Bob />);
27+
const renderWrapper = mount(<Bob />, { context: contextStub });
2028
expect(setStateSpy.callCount).toEqual(0);
2129
renderWrapper.unmount();
2230
return new Promise(resolve => setTimeout(resolve, resolveDelay + 2))

src/createAsyncComponent.js

Lines changed: 27 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -46,22 +46,24 @@ function createAsyncComponent(args) {
4646

4747
this.state = { Component: null };
4848

49-
if (asyncComponents) {
50-
const { nextId, getComponent } = asyncComponents;
51-
if (!id) {
52-
id = nextId();
53-
}
54-
const Component = es6Resolve(getComponent(id));
55-
if (Component) {
56-
this.state = { Component };
57-
} else {
58-
this.getAsyncComponentData = () => ({
59-
id,
60-
defer: ssrMode === 'defer'
61-
|| (asyncComponentsAncestor && asyncComponentsAncestor.isBoundary),
62-
getResolver,
63-
});
64-
}
49+
// Assign a unique id to this instance if it hasn't already got one.
50+
// Note: the closure usage.
51+
const { getNextId, getComponent } = asyncComponents;
52+
if (!id) {
53+
id = getNextId();
54+
}
55+
56+
// Try resolve the component.
57+
const Component = es6Resolve(getComponent(id));
58+
if (Component) {
59+
this.state = { Component };
60+
} else {
61+
this.getAsyncComponentData = () => ({
62+
id,
63+
defer: ssrMode === 'defer'
64+
|| (asyncComponentsAncestor && asyncComponentsAncestor.isBoundary),
65+
getResolver: () => this.resolveComponent(),
66+
});
6567
}
6668
}
6769

@@ -88,9 +90,12 @@ function createAsyncComponent(args) {
8890
// The component is unmounted, so no need to set the state.
8991
return;
9092
}
91-
this.setState({
92-
Component: es6Resolve(Component),
93-
});
93+
this.context.asyncComponents.registerComponent(id, Component);
94+
if (this.setState) {
95+
this.setState({
96+
Component: es6Resolve(Component),
97+
});
98+
}
9499
});
95100
}
96101

@@ -117,9 +122,10 @@ function createAsyncComponent(args) {
117122

118123
AsyncComponent.contextTypes = {
119124
asyncComponents: React.PropTypes.shape({
120-
nextId: React.PropTypes.func.isRequired,
125+
getNextId: React.PropTypes.func.isRequired,
121126
getComponent: React.PropTypes.func.isRequired,
122-
}),
127+
registerComponent: React.PropTypes.func.isRequired,
128+
}).isRequired,
123129
};
124130

125131
AsyncComponent.displayName = name || 'AsyncComponent';

src/types.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,15 @@ import { Element } from 'react';
66
export type React$Element = Element<*>;
77

88
export type ExecContext = {
9+
getNextId : () => number,
910
registerComponent : (number, Function) => void,
1011
getComponent : (number) => ?Function,
1112
getResolved : () => { [key : number] : true },
1213
}
1314

1415
export type ProviderChildContext = {
1516
asyncComponents: {
16-
nextId : () => number,
17+
getNextId : () => number,
1718
getComponent : (number) => ?Function,
1819
}
1920
};

src/withAsyncComponents.js

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,13 @@ import { STATE_IDENTIFIER } from './constants';
77
import type { React$Element } from './types';
88

99
function createExecContext() {
10+
let idPointer = 0;
1011
const registry = {};
1112
return {
13+
getNextId: () => {
14+
idPointer += 1;
15+
return idPointer;
16+
},
1217
registerComponent(id, Component) {
1318
registry[id] = Component;
1419
},
@@ -59,7 +64,7 @@ export default function withAsyncComponents(app : React$Element) {
5964
return false;
6065
}
6166

62-
const resolver = getResolver().then(C => execContext.registerComponent(id, C));
67+
const resolver = getResolver();
6368
resolvers.push({
6469
resolver,
6570
element,

0 commit comments

Comments
 (0)