Skip to content

Commit b18649e

Browse files
committed
Merge pull request #70 from rackt/parent-context
Use parent context for <Provider> in React 0.14
2 parents ef2c39f + b8d7ee6 commit b18649e

File tree

4 files changed

+243
-78
lines changed

4 files changed

+243
-78
lines changed

package.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,6 @@
5252
"mocha-jsdom": "~0.4.0",
5353
"react": "^0.14.0-beta3",
5454
"react-addons-test-utils": "^0.14.0-beta3",
55-
"react-dom": "^0.14.0-beta3",
5655
"redux": "^1.0.1",
5756
"rimraf": "^2.3.4",
5857
"webpack": "^1.11.0"

src/components/createProvider.js

Lines changed: 55 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,59 @@
11
import createStoreShape from '../utils/createStoreShape';
22

3+
function isUsingOwnerContext(React) {
4+
const { version } = React;
5+
if (typeof version !== 'string') {
6+
return false;
7+
}
8+
9+
const sections = version.split('.');
10+
const major = parseInt(sections[0], 10);
11+
const minor = parseInt(sections[1], 10);
12+
13+
return major === 0 && minor === 13;
14+
}
15+
316
export default function createProvider(React) {
4-
const { Component, PropTypes } = React;
17+
const { Component, PropTypes, Children } = React;
518
const storeShape = createStoreShape(PropTypes);
19+
const requireFunctionChild = isUsingOwnerContext(React);
20+
21+
let didWarn = false;
22+
function warnAboutFunction() {
23+
if (didWarn || requireFunctionChild) {
24+
return;
25+
}
26+
27+
didWarn = true;
28+
console.error( // eslint-disable-line no-console
29+
'With React 0.14 and later versions, you no longer need to ' +
30+
'wrap <Provider> child into a function.'
31+
);
32+
}
33+
function warnAboutElement() {
34+
if (didWarn || !requireFunctionChild) {
35+
return;
36+
}
37+
38+
didWarn = true;
39+
console.error( // eslint-disable-line no-console
40+
'With React 0.13, you need to ' +
41+
'wrap <Provider> child into a function. ' +
42+
'This restriction will be removed with React 0.14.'
43+
);
44+
}
645

746
return class Provider extends Component {
847
static childContextTypes = {
948
store: storeShape.isRequired
1049
};
1150

1251
static propTypes = {
13-
children: PropTypes.func.isRequired,
14-
store: storeShape.isRequired
52+
store: storeShape.isRequired,
53+
children: (requireFunctionChild ?
54+
PropTypes.func :
55+
PropTypes.element
56+
).isRequired
1557
};
1658

1759
getChildContext() {
@@ -34,8 +76,16 @@ export default function createProvider(React) {
3476
}
3577

3678
render() {
37-
const { children } = this.props;
38-
return children();
79+
let { children } = this.props;
80+
81+
if (typeof children === 'function') {
82+
warnAboutFunction();
83+
children = children();
84+
} else {
85+
warnAboutElement();
86+
}
87+
88+
return Children.only(children);
3989
}
4090
};
4191
}

test/components/Provider.spec.js

Lines changed: 140 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import React, { PropTypes, Component } from 'react';
44
import TestUtils from 'react-addons-test-utils';
55
import { createStore } from 'redux';
66
import { Provider } from '../../src/index';
7+
import createProvider from '../../src/components/createProvider';
78

89
describe('React', () => {
910
describe('Provider', () => {
@@ -19,14 +20,152 @@ describe('React', () => {
1920
}
2021
}
2122

23+
it('should not warn when using child-as-a-function before React 0.14', () => {
24+
const store = createStore(() => ({}));
25+
['0.13.0-beta', '0.13.0', '0.13.3'].forEach(version => {
26+
const LocalProvider = createProvider({ ...React, version });
27+
28+
let spy = expect.spyOn(console, 'error');
29+
const tree = TestUtils.renderIntoDocument(
30+
<LocalProvider store={store}>
31+
{() => <Child />}
32+
</LocalProvider>
33+
);
34+
spy.destroy();
35+
expect(spy.calls.length).toBe(0);
36+
37+
spy = expect.spyOn(console, 'error');
38+
tree.forceUpdate();
39+
spy.destroy();
40+
expect(spy.calls.length).toBe(0);
41+
});
42+
});
43+
44+
it('should warn once when using a single element before React 0.14', () => {
45+
const store = createStore(() => ({}));
46+
['0.13.0-beta', '0.13.0', '0.13.3'].forEach(version => {
47+
const LocalProvider = createProvider({ ...React, version });
48+
// Trick React into checking propTypes every time:
49+
LocalProvider.displayName = Math.random().toString();
50+
51+
let spy = expect.spyOn(console, 'error');
52+
const tree = TestUtils.renderIntoDocument(
53+
<LocalProvider store={store}>
54+
<Child />
55+
</LocalProvider>
56+
);
57+
spy.destroy();
58+
59+
expect(spy.calls.length).toBe(2);
60+
expect(spy.calls[0].arguments[0]).toMatch(
61+
/Invalid prop `children` of type `object` supplied to .*, expected `function`./
62+
);
63+
expect(spy.calls[1].arguments[0]).toMatch(
64+
/With React 0.13, you need to wrap <Provider> child into a function. This restriction will be removed with React 0.14./
65+
);
66+
67+
spy = expect.spyOn(console, 'error');
68+
tree.forceUpdate();
69+
spy.destroy();
70+
expect(spy.calls.length).toBe(0);
71+
});
72+
});
73+
74+
it('should warn once when using child-as-a-function after React 0.14', () => {
75+
const store = createStore(() => ({}));
76+
['0.14.0-beta3', '0.14.0', '0.14.2', '0.15.0-beta', '1.0.0-beta', '1.0.0'].forEach(version => {
77+
const LocalProvider = createProvider({ ...React, version });
78+
// Trick React into checking propTypes every time:
79+
LocalProvider.displayName = Math.random().toString();
80+
81+
let spy = expect.spyOn(console, 'error');
82+
const tree = TestUtils.renderIntoDocument(
83+
<LocalProvider store={store}>
84+
{() => <Child />}
85+
</LocalProvider>
86+
);
87+
spy.destroy();
88+
89+
expect(spy.calls.length).toBe(2);
90+
expect(spy.calls[0].arguments[0]).toMatch(
91+
/Invalid prop `children` supplied to .*, expected a single ReactElement./
92+
);
93+
expect(spy.calls[1].arguments[0]).toMatch(
94+
/With React 0.14 and later versions, you no longer need to wrap <Provider> child into a function./
95+
);
96+
97+
spy = expect.spyOn(console, 'error');
98+
tree.forceUpdate();
99+
spy.destroy();
100+
expect(spy.calls.length).toBe(0);
101+
});
102+
});
103+
104+
it('should enforce a single child', () => {
105+
const store = createStore(() => ({}));
106+
107+
expect(() => TestUtils.renderIntoDocument(
108+
<Provider store={store}>
109+
<div />
110+
</Provider>
111+
)).toNotThrow();
112+
113+
expect(() => TestUtils.renderIntoDocument(
114+
<Provider store={store}>
115+
</Provider>
116+
)).toThrow(/exactly one child/);
117+
118+
expect(() => TestUtils.renderIntoDocument(
119+
<Provider store={store}>
120+
<div />
121+
<div />
122+
</Provider>
123+
)).toThrow(/exactly one child/);
124+
});
125+
126+
it('should enforce a single child when using function-as-a-child', () => {
127+
const store = createStore(() => ({}));
128+
129+
expect(() => TestUtils.renderIntoDocument(
130+
<Provider store={store}>
131+
{() => <div />}
132+
</Provider>
133+
)).toNotThrow();
134+
135+
expect(() => TestUtils.renderIntoDocument(
136+
<Provider store={store}>
137+
{() => {}}
138+
</Provider>
139+
)).toThrow(/exactly one child/);
140+
});
141+
22142
it('should add the store to the child context', () => {
23143
const store = createStore(() => ({}));
24144

145+
const spy = expect.spyOn(console, 'error');
146+
const tree = TestUtils.renderIntoDocument(
147+
<Provider store={store}>
148+
<Child />
149+
</Provider>
150+
);
151+
spy.destroy();
152+
expect(spy.calls.length).toBe(0);
153+
154+
const child = TestUtils.findRenderedComponentWithType(tree, Child);
155+
expect(child.context.store).toBe(store);
156+
});
157+
158+
it('should add the store to the child context with function-as-a-child', () => {
159+
const store = createStore(() => ({}));
160+
161+
const spy = expect.spyOn(console, 'error');
25162
const tree = TestUtils.renderIntoDocument(
26163
<Provider store={store}>
27164
{() => <Child />}
28165
</Provider>
29166
);
167+
spy.destroy();
168+
expect(spy.calls.length).toBe(0);
30169

31170
const child = TestUtils.findRenderedComponentWithType(tree, Child);
32171
expect(child.context.store).toBe(store);
@@ -43,7 +182,7 @@ describe('React', () => {
43182
render() {
44183
return (
45184
<Provider store={this.state.store}>
46-
{() => <Child />}
185+
<Child />
47186
</Provider>
48187
);
49188
}

0 commit comments

Comments
 (0)