Skip to content

Commit 9ceb46a

Browse files
author
rodik
committed
New interface for @asyncConnect decorator,
router in decorator params, ability to use groups for props
1 parent 7d273f0 commit 9ceb46a

File tree

2 files changed

+88
-68
lines changed

2 files changed

+88
-68
lines changed

modules/ReduxAsyncConnect.js

Lines changed: 38 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -35,16 +35,34 @@ function filterAndFlattenComponents(components) {
3535
return flattened;
3636
}
3737

38-
function asyncConnectPromises(components, params, store, helpers) {
39-
return components.map(Component => Component.reduxAsyncConnect(params, store, helpers))
40-
.filter(result => result && result.then instanceof Function);
38+
function loadAsyncConnect({components, filter = () => true, ...rest}) {
39+
let async = false;
40+
const promise = Promise.all(filterAndFlattenComponents(components).map(Component => {
41+
const asyncItems = Component.reduxAsyncConnect;
42+
43+
return Promise.all(asyncItems.reduce((itemsResults, item) => {
44+
if (filter(item, Component)) {
45+
let promiseOrResult = item.promise(rest);
46+
if (promiseOrResult && promiseOrResult.then instanceof Function) {
47+
async = true;
48+
promiseOrResult = promiseOrResult.catch(error => ({error}));
49+
}
50+
return [...itemsResults, promiseOrResult];
51+
} else {
52+
return itemsResults;
53+
}
54+
}, [])).then(results => {
55+
return asyncItems.reduce((result, item, i) => ({...result, [item.key]: results[i]}), {});
56+
});
57+
}));
58+
59+
return {promise, async};
4160
}
4261

43-
export function loadOnServer({ components, params }, store, helpers) {
44-
return Promise.all(asyncConnectPromises(filterAndFlattenComponents(components), params, store, helpers))
45-
.catch(error => console.error('reduxAsyncConnect server promise error: ', error)).then(() => {
46-
store.dispatch(endGlobalLoad());
47-
});
62+
export function loadOnServer(args) {
63+
return loadAsyncConnect(args).promise.then(() => {
64+
args.store.dispatch(endGlobalLoad());
65+
});
4866
}
4967

5068
let loadDataCounter = 0;
@@ -98,26 +116,24 @@ class ReduxAsyncConnect extends React.Component {
98116
}
99117

100118
loadAsyncData(props) {
101-
const { components, params, helpers } = props;
102119
const store = this.context.store;
103-
const promises = asyncConnectPromises(filterAndFlattenComponents(components), params, store, helpers);
120+
const loadResult = loadAsyncConnect({...props, store});
104121

105122
loadDataCounter++;
106123

107-
if (promises.length) {
124+
if (loadResult.async) {
108125
this.props.beginGlobalLoad();
109126
(loadDataCounterOriginal => {
110-
Promise.all(promises).catch(error => console.error('reduxAsyncConnect server promise error: ', error))
111-
.then(() => {
112-
// We need to change propsToShow only if loadAsyncData that called this promise
113-
// is the last invocation of loadAsyncData method. Otherwise we can face situation
114-
// when user is changing route several times and we finally show him route that has
115-
// loaded props last time and not the last called route
116-
if (loadDataCounter === loadDataCounterOriginal) {
117-
this.setState({propsToShow: props});
118-
}
119-
this.props.endGlobalLoad();
120-
});
127+
loadResult.promise.then(() => {
128+
// We need to change propsToShow only if loadAsyncData that called this promise
129+
// is the last invocation of loadAsyncData method. Otherwise we can face situation
130+
// when user is changing route several times and we finally show him route that has
131+
// loaded props last time and not the last called route
132+
if (loadDataCounter === loadDataCounterOriginal) {
133+
this.setState({propsToShow: props});
134+
}
135+
this.props.endGlobalLoad();
136+
});
121137
})(loadDataCounter);
122138
} else {
123139
this.setState({propsToShow: props});

modules/asyncConnect.js

Lines changed: 50 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,6 @@ export const BEGIN_GLOBAL_LOAD = 'reduxAsyncConnect/BEGIN_GLOBAL_LOAD';
88
export const END_GLOBAL_LOAD = 'reduxAsyncConnect/END_GLOBAL_LOAD';
99

1010
export function reducer(state = {loaded: false}, action = {}) {
11-
const stateSlice = state[action.key];
12-
1311
switch (action.type) {
1412
case BEGIN_GLOBAL_LOAD:
1513
return {
@@ -19,45 +17,53 @@ export function reducer(state = {loaded: false}, action = {}) {
1917
case END_GLOBAL_LOAD:
2018
return {
2119
...state,
22-
2320
loaded: true
2421
};
2522
case LOAD:
2623
return {
2724
...state,
28-
[action.key]: {
29-
...stateSlice,
30-
loading: true,
31-
loaded: false
25+
loadState: {
26+
[action.key]: {
27+
loading: true,
28+
loaded: false
29+
}
3230
}
3331
};
3432
case LOAD_SUCCESS:
3533
return {
3634
...state,
37-
[action.key]: {
38-
...stateSlice,
39-
loading: false,
40-
loaded: true,
41-
data: action.data
42-
}
35+
loadState: {
36+
[action.key]: {
37+
loading: false,
38+
loaded: true,
39+
error: null
40+
}
41+
},
42+
[action.key]: action.data
4343
};
4444
case LOAD_FAIL:
4545
return {
4646
...state,
47-
[action.key]: {
48-
...stateSlice,
49-
loading: false,
50-
loaded: false,
51-
error: action.error
52-
}
47+
loadState: {
48+
[action.key]: {
49+
loading: false,
50+
loaded: false,
51+
error: action.error
52+
}
53+
},
54+
[action.key]: null
5355
};
5456
case CLEAR:
5557
return {
5658
...state,
57-
[action.key]: {
58-
loaded: false,
59-
loading: false
60-
}
59+
loadState: {
60+
[action.key]: {
61+
loading: false,
62+
loaded: false,
63+
error: null
64+
}
65+
},
66+
[action.key]: null
6167
};
6268
default:
6369
return state;
@@ -102,36 +108,34 @@ function loadFail(key, error) {
102108
};
103109
}
104110

105-
function componentLoadCb(mapStateToProps, params, store, helpers) {
106-
const dispatch = store.dispatch;
107-
108-
const promises = Object.keys(mapStateToProps).reduce((result, key) => {
109-
let promiseOrResult = mapStateToProps[key](params, {...helpers, store});
110-
111-
if (promiseOrResult !== undefined) {
112-
if (promiseOrResult.then instanceof Function) {
113-
dispatch(load(key));
114-
promiseOrResult = promiseOrResult
115-
.then(nextData => dispatch(loadSuccess(key, nextData)),
116-
err => dispatch(loadFail(key, err)));
111+
function wrapWithDispatch(asyncItems) {
112+
return asyncItems.map(item =>
113+
item.key ? {...item, promise: (options) => {
114+
const {dispatch} = options.store;
115+
const promiseOrResult = item.promise(options);
116+
if (promiseOrResult !== undefined) {
117+
if (promiseOrResult.then instanceof Function) {
118+
dispatch(load(item.key));
119+
promiseOrResult.then(data => dispatch(loadSuccess(item.key, data)))
120+
.catch(err => dispatch(loadFail(item.key, err)));
121+
}
117122

118-
return [...result, promiseOrResult];
123+
dispatch(loadSuccess(item.key, promiseOrResult));
119124
}
120-
121-
dispatch(loadSuccess(key, promiseOrResult));
122-
}
123-
return [...result];
124-
}, []);
125-
126-
return promises.length === 0 ? null : Promise.all(promises);
125+
return promiseOrResult;
126+
}} : item
127+
);
127128
}
128129

129-
export function asyncConnect(mapStateToProps) {
130+
export function asyncConnect(asyncItems) {
130131
return Component => {
131-
Component.reduxAsyncConnect = (params, store, helpers) => componentLoadCb(mapStateToProps, params, store, helpers);
132+
Component.reduxAsyncConnect = wrapWithDispatch(asyncItems);
132133

133134
const finalMapStateToProps = state => {
134-
return Object.keys(mapStateToProps).reduce((result, key) => ({...result, [key]: state.reduxAsyncConnect[key]}), {});
135+
return asyncItems.reduce((result, item) =>
136+
item.key ? {...result, [item.key]: state.reduxAsyncConnect[item.key]} : result,
137+
{}
138+
);
135139
};
136140

137141
return connect(finalMapStateToProps)(Component);

0 commit comments

Comments
 (0)