Skip to content

Commit 2d3d0be

Browse files
committed
Make refs opt-in. Fixes #141
1 parent c54ffd9 commit 2d3d0be

File tree

2 files changed

+61
-7
lines changed

2 files changed

+61
-7
lines changed

src/components/connect.js

+8-4
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ export default function connect(mapStateToProps, mapDispatchToProps, mergeProps,
3030
const finalMergeProps = mergeProps || defaultMergeProps;
3131
const shouldUpdateStateProps = finalMapStateToProps.length > 1;
3232
const shouldUpdateDispatchProps = finalMapDispatchToProps.length > 1;
33-
const { pure = true } = options;
33+
const { pure = true, withRef = false } = options;
3434

3535
// Helps track hot reloading.
3636
const version = nextVersion++;
@@ -75,7 +75,6 @@ export default function connect(mapStateToProps, mapDispatchToProps, mergeProps,
7575

7676
return function wrapWithConnect(WrappedComponent) {
7777
class Connect extends Component {
78-
7978
shouldComponentUpdate(nextProps, nextState) {
8079
if (!pure) {
8180
this.updateStateProps(nextProps);
@@ -192,13 +191,18 @@ export default function connect(mapStateToProps, mapDispatchToProps, mergeProps,
192191
}
193192

194193
getWrappedInstance() {
194+
invariant(withRef,
195+
`To access the wrapped instance, you need to specify ` +
196+
`{ withRef: true } as the fourth argument of the connect() call.`
197+
);
198+
195199
return this.refs.wrappedInstance;
196200
}
197201

198202
render() {
203+
const ref = withRef ? 'wrappedInstance' : null;
199204
return (
200-
<WrappedComponent ref='wrappedInstance'
201-
{...this.nextState} />
205+
<WrappedComponent {...this.nextState} ref={ref} />
202206
);
203207
}
204208
}

test/components/connect.spec.js

+53-3
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ describe('React', () => {
8585
).toNotThrow();
8686
});
8787

88-
it('should subscribe to the store changes', () => {
88+
it('should subscribe class components to the store changes', () => {
8989
const store = createStore(stringBuilder);
9090

9191
@connect(state => ({ string: state }) )
@@ -109,6 +109,32 @@ describe('React', () => {
109109
expect(stub.props.string).toBe('ab');
110110
});
111111

112+
it('should subscribe pure function components to the store changes', () => {
113+
const store = createStore(stringBuilder);
114+
115+
let Container = connect(
116+
state => ({ string: state })
117+
)(function Container(props) {
118+
return <Passthrough {...props}/>;
119+
});
120+
121+
const spy = expect.spyOn(console, 'error');
122+
const tree = TestUtils.renderIntoDocument(
123+
<ProviderMock store={store}>
124+
<Container />
125+
</ProviderMock>
126+
);
127+
spy.destroy();
128+
expect(spy.calls.length).toBe(0);
129+
130+
const stub = TestUtils.findRenderedComponentWithType(tree, Passthrough);
131+
expect(stub.props.string).toBe('');
132+
store.dispatch({ type: 'APPEND', body: 'a'});
133+
expect(stub.props.string).toBe('a');
134+
store.dispatch({ type: 'APPEND', body: 'b'});
135+
expect(stub.props.string).toBe('ab');
136+
});
137+
112138
it('should handle dispatches before componentDidMount', () => {
113139
const store = createStore(stringBuilder);
114140

@@ -1084,6 +1110,30 @@ describe('React', () => {
10841110
);
10851111
});
10861112

1113+
it('should throw when trying to access the wrapped instance if withRef is not specified', () => {
1114+
const store = createStore(() => ({}));
1115+
1116+
class Container extends Component {
1117+
render() {
1118+
return <Passthrough />;
1119+
}
1120+
}
1121+
1122+
const decorator = connect(state => state);
1123+
const Decorated = decorator(Container);
1124+
1125+
const tree = TestUtils.renderIntoDocument(
1126+
<ProviderMock store={store}>
1127+
<Decorated />
1128+
</ProviderMock>
1129+
);
1130+
1131+
const decorated = TestUtils.findRenderedComponentWithType(tree, Decorated);
1132+
expect(() => decorated.getWrappedInstance()).toThrow(
1133+
/To access the wrapped instance, you need to specify \{ withRef: true \} as the fourth argument of the connect\(\) call\./
1134+
);
1135+
});
1136+
10871137
it('should return the instance of the wrapped component for use in calling child methods', () => {
10881138
const store = createStore(() => ({}));
10891139

@@ -1101,7 +1151,7 @@ describe('React', () => {
11011151
}
11021152
}
11031153

1104-
const decorator = connect(state => state);
1154+
const decorator = connect(state => state, null, null, { withRef: true });
11051155
const Decorated = decorator(Container);
11061156

11071157
const tree = TestUtils.renderIntoDocument(
@@ -1231,7 +1281,7 @@ describe('React', () => {
12311281
store.dispatch({ type: 'APPEND', body: 'a'});
12321282
let childMapStateInvokes = 0;
12331283

1234-
@connect(state => ({ state }))
1284+
@connect(state => ({ state }), null, null, { withRef: true })
12351285
class Container extends Component {
12361286

12371287
emitChange() {

0 commit comments

Comments
 (0)