Skip to content

Commit f6ef06e

Browse files
committed
WIP connectWrapper that will eventually replace connectDecorator and Connector
1 parent faa69b6 commit f6ef06e

File tree

10 files changed

+323
-18
lines changed

10 files changed

+323
-18
lines changed

src/components/createAll.js

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import createProvideDecorator from './createProvideDecorator';
33

44
import createConnector from './createConnector';
55
import createConnectDecorator from './createConnectDecorator';
6+
import createConnectWrapper from './createConnectWrapper';
67

78
export default function createAll(React) {
89
// Wrapper components
@@ -11,7 +12,10 @@ export default function createAll(React) {
1112

1213
// Higher-order components (decorators)
1314
const provide = createProvideDecorator(React, Provider);
14-
const connect = createConnectDecorator(React, Connector);
15+
const connectDecorate = createConnectDecorator(React, Connector);
16+
const connect = createConnectWrapper(React);
1517

16-
return { Provider, Connector, provide, connect };
18+
19+
20+
return { Provider, Connector, provide, connectDecorate, connect };
1721
}
Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
import createStoreShape from '../utils/createStoreShape';
2+
import getDisplayName from '../utils/getDisplayName';
3+
import shallowEqual from '../utils/shallowEqual';
4+
import isPlainObject from '../utils/isPlainObject';
5+
import wrapActionCreators from '../utils/wrapActionCreators';
6+
import invariant from 'invariant';
7+
8+
const emptySelector = () => ({});
9+
10+
const emptyBinder = () => ({});
11+
12+
const identityMerge = (slice, actionsCreators, props) => ({...slice, ...actionsCreators, ...props});
13+
14+
15+
export default function createConnectWrapper(React) {
16+
const { Component, PropTypes } = React;
17+
const storeShape = createStoreShape(PropTypes);
18+
19+
return function connect(select, bindActionCreators, merge) {
20+
21+
const subscribing = select ? true : false;
22+
23+
select = select || emptySelector;
24+
25+
bindActionCreators = bindActionCreators || emptyBinder;
26+
27+
if (isPlainObject(bindActionCreators)) {
28+
bindActionCreators = wrapActionCreators(bindActionCreators);
29+
}
30+
31+
merge = merge || identityMerge;
32+
33+
return DecoratedComponent => class ConnectWrapper extends Component {
34+
static displayName = `ConnectWrapper(${getDisplayName(DecoratedComponent)})`;
35+
static DecoratedComponent = DecoratedComponent;
36+
37+
static contextTypes = {
38+
store: storeShape.isRequired
39+
};
40+
41+
componentWillReceiveProps(nextProps) {
42+
console.log('recieving props', this.props, nextProps)
43+
}
44+
45+
shouldComponentUpdate(nextProps, nextState) {
46+
console.log('shallowEqual of props', shallowEqual(this.props, nextProps), this.props, nextProps)
47+
return (this.subscribed && !this.isSliceEqual(this.state.slice, nextState.slice)) ||
48+
!shallowEqual(this.props, nextProps);
49+
}
50+
51+
isSliceEqual(slice, nextSlice) {
52+
const isRefEqual = slice === nextSlice;
53+
if (isRefEqual) {
54+
return true;
55+
} else if (typeof slice !== 'object' || typeof nextSlice !== 'object') {
56+
return isRefEqual;
57+
}
58+
return shallowEqual(slice, nextSlice);
59+
}
60+
61+
constructor(props, context) {
62+
super(props, context);
63+
this.state = {
64+
...this.selectState(props, context),
65+
...this.bindActionCreators(context),
66+
};
67+
}
68+
69+
componentWillMount() {
70+
console.log('will mount', this.props)
71+
}
72+
73+
componentDidMount() {
74+
console.log('mounted', this.props)
75+
if (subscribing) {
76+
this.subscribed = true;
77+
this.unsubscribe = this.context.store.subscribe(::this.handleChange);
78+
}
79+
}
80+
81+
componentWillUnmount() {
82+
if (subscribing) {
83+
this.unsubscribe();
84+
}
85+
}
86+
87+
handleChange(props = this.props) {
88+
const nextState = this.selectState(props, this.context);
89+
if (!this.isSliceEqual(this.state.slice, nextState.slice)) {
90+
this.setState(nextState);
91+
}
92+
}
93+
94+
selectState(props = this.props, context = this.context) {
95+
const state = context.store.getState();
96+
const slice = select(state);
97+
98+
invariant(
99+
isPlainObject(slice),
100+
'The return value of `select` prop must be an object. Instead received %s.',
101+
slice
102+
);
103+
104+
return { slice };
105+
}
106+
107+
bindActionCreators(context = this.context) {
108+
const { dispatch } = context.store;
109+
const actionCreators = bindActionCreators(dispatch);
110+
111+
invariant(
112+
isPlainObject(actionCreators),
113+
'The return value of `bindActionCreators` prop must be an object. Instead received %s.',
114+
actionCreators
115+
);
116+
117+
return { actionCreators };
118+
}
119+
120+
merge(props = this.props, state = this.state) {
121+
const { slice, actionCreators } = state;
122+
const merged = merge(slice, actionCreators, props);
123+
124+
invariant(
125+
isPlainObject(merged),
126+
'The return value of `merge` prop must be an object. Instead received %s.',
127+
merged
128+
);
129+
130+
console.log('merging with ', merged)
131+
132+
return merged;
133+
}
134+
135+
render() {
136+
return <DecoratedComponent {...this.merge()} />;
137+
}
138+
};
139+
};
140+
}

src/index.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
import React from 'react';
22
import createAll from './components/createAll';
33

4-
export const { Provider, Connector, provide, connect } = createAll(React);
4+
export const { Provider, Connector, provide, connectDecorate, connect } = createAll(React);

src/native.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
import React from 'react-native';
22
import createAll from './components/createAll';
33

4-
export const { Provider, Connector, provide, connect } = createAll(React);
4+
export const { Provider, Connector, provide, connectDecorate, connect } = createAll(React);

src/utils/wrapActionCreators.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import { bindActionCreators } from 'redux'
2+
3+
export default function wrapActionCreators (actionCreators) {
4+
return dispatch => bindActionCreators(actionCreators, dispatch);
5+
}

test/components/Connector.spec.js

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import expect from 'expect';
22
import jsdomReact from './jsdomReact';
33
import React, { PropTypes, Component } from 'react/addons';
4-
import { createStore } from 'redux';
4+
import { createStore, combineReducers } from 'redux';
55
import { Connector } from '../../src/index';
66

77
const { TestUtils } = React.addons;
@@ -32,7 +32,7 @@ describe('React', () => {
3232
}
3333

3434
it('should receive the store in the context', () => {
35-
const store = createStore({});
35+
const store = createStore(() => ({}));
3636

3737
const tree = TestUtils.renderIntoDocument(
3838
<Provider store={store}>
@@ -74,7 +74,7 @@ describe('React', () => {
7474
const subscribe = store.subscribe;
7575

7676
// Keep track of unsubscribe by wrapping subscribe()
77-
const spy = expect.createSpy(() => {});
77+
const spy = expect.createSpy(() => ({}));
7878
store.subscribe = (listener) => {
7979
const unsubscribe = subscribe(listener);
8080
return () => {
@@ -101,7 +101,7 @@ describe('React', () => {
101101

102102
it('should shallowly compare the selected state to prevent unnecessary updates', () => {
103103
const store = createStore(stringBuilder);
104-
const spy = expect.createSpy(() => {});
104+
const spy = expect.createSpy(() => ({}));
105105
function render({ string }) {
106106
spy();
107107
return <div string={string}/>;
@@ -129,10 +129,10 @@ describe('React', () => {
129129
});
130130

131131
it('should recompute the state slice when the select prop changes', () => {
132-
const store = createStore({
132+
const store = createStore(combineReducers({
133133
a: () => 42,
134134
b: () => 72
135-
});
135+
}));
136136

137137
function selectA(state) {
138138
return { result: state.a };
@@ -174,7 +174,7 @@ describe('React', () => {
174174
});
175175

176176
it('should pass dispatch() to the child function', () => {
177-
const store = createStore({});
177+
const store = createStore(() => ({}));
178178

179179
const tree = TestUtils.renderIntoDocument(
180180
<Provider store={store}>
@@ -191,7 +191,7 @@ describe('React', () => {
191191
});
192192

193193
it('should throw an error if select returns anything but a plain object', () => {
194-
const store = createStore({});
194+
const store = createStore(() => ({}));
195195

196196
expect(() => {
197197
TestUtils.renderIntoDocument(

test/components/Provider.spec.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ describe('React', () => {
2121
}
2222

2323
it('should add the store to the child context', () => {
24-
const store = createStore({});
24+
const store = createStore(() => ({}));
2525

2626
const tree = TestUtils.renderIntoDocument(
2727
<Provider store={store}>
@@ -36,7 +36,7 @@ describe('React', () => {
3636
it('should replace just the reducer when receiving a new store in props', () => {
3737
const store1 = createStore((state = 10) => state + 1);
3838
const store2 = createStore((state = 10) => state * 2);
39-
const spy = expect.createSpy(() => {});
39+
const spy = expect.createSpy(() => ({}));
4040

4141
class ProviderContainer extends Component {
4242
state = { store: store1 };

test/components/connect.spec.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import expect from 'expect';
22
import jsdomReact from './jsdomReact';
33
import React, { PropTypes, Component } from 'react/addons';
44
import { createStore } from 'redux';
5-
import { connect, Connector } from '../../src/index';
5+
import { connect } from '../../src/index';
66

77
const { TestUtils } = React.addons;
88

@@ -46,7 +46,7 @@ describe('React', () => {
4646
expect(div.props.pass).toEqual('through');
4747
expect(div.props.foo).toEqual('bar');
4848
expect(() =>
49-
TestUtils.findRenderedComponentWithType(container, Connector)
49+
TestUtils.findRenderedComponentWithType(container, Container)
5050
).toNotThrow();
5151
});
5252

0 commit comments

Comments
 (0)