Skip to content

Commit 4153879

Browse files
author
Ryan Ashcraft
committed
Requests reducer with async action
1 parent 3e56f94 commit 4153879

File tree

10 files changed

+156
-7
lines changed

10 files changed

+156
-7
lines changed

.babelrc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
{
2-
"presets": ["es2015", "stage-2"]
2+
"presets": ["react", "es2015", "stage-2"]
33
}

examples/async/containers/App.js

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import React, { Component, PropTypes } from 'react'
22
import { connect } from 'react-redux'
3+
import { createContainer, Schema, arrayOf } from 'redux-query';
34
import { selectReddit, fetchPostsIfNeeded, invalidateReddit } from '../actions'
45
import Picker from '../components/Picker'
56
import Posts from '../components/Posts'
@@ -99,4 +100,21 @@ function mapStateToProps(state) {
99100
}
100101
}
101102

102-
export default connect(mapStateToProps)(App)
103+
const post = new Schema('posts', {
104+
idAttribute: (entity) => {
105+
return entity.data.id;
106+
}
107+
});
108+
109+
const AppContainer = createContainer((props) => {
110+
return {
111+
url: `https://www.reddit.com/r/${props.selectedReddit}.json`,
112+
schema: {
113+
data: {
114+
children: arrayOf(post)
115+
}
116+
}
117+
};
118+
}, (state) => state.requests)(App);
119+
120+
export default connect(mapStateToProps)(AppContainer)

examples/async/reducers/index.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import {
33
SELECT_REDDIT, INVALIDATE_REDDIT,
44
REQUEST_POSTS, RECEIVE_POSTS
55
} from '../actions'
6+
import { requestsReducer } from 'redux-query';
67

78
function selectedReddit(state = 'reactjs', action) {
89
switch (action.type) {
@@ -55,7 +56,8 @@ function postsByReddit(state = { }, action) {
5556

5657
const rootReducer = combineReducers({
5758
postsByReddit,
58-
selectedReddit
59+
selectedReddit,
60+
requests: requestsReducer
5961
})
6062

6163
export default rootReducer

examples/async/webpack.config.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ var path = require('path')
22
var webpack = require('webpack')
33

44
module.exports = {
5-
devtool: 'cheap-module-eval-source-map',
5+
devtool: 'cheap-source-map',
66
entry: [
77
'webpack-hot-middleware/client',
88
'./index'
@@ -14,6 +14,7 @@ module.exports = {
1414
},
1515
plugins: [
1616
new webpack.optimize.OccurenceOrderPlugin(),
17+
new webpack.optimize.DedupePlugin(),
1718
new webpack.HotModuleReplacementPlugin(),
1819
new webpack.NoErrorsPlugin()
1920
],

package.json

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,16 +17,24 @@
1717
"author": "Ryan Ashcraft",
1818
"license": "ISC",
1919
"devDependencies": {
20-
"babel-cli": "^6.1.2",
2120
"babel-core": "^6.2.1",
2221
"babel-loader": "^6.2.0",
23-
"babel-preset-es2015": "^6.1.2",
24-
"babel-preset-stage-2": "^6.3.13",
22+
"babel-preset-es2015": "^6.3.13",
23+
"babel-preset-react": "^6.3.13",
2524
"react": "^0.14.6",
2625
"react-redux": "^4.0.6",
2726
"redux": "^3.0.5",
2827
"redux-devtools": "^3.0.0",
2928
"redux-devtools-log-monitor": "^1.0.1",
3029
"webpack": "^1.12.9"
30+
},
31+
"dependencies": {
32+
"babel-preset-stage-2": "^6.3.13",
33+
"lodash": "^4.0.0",
34+
"normalizr": "^2.0.0",
35+
"superagent": "^1.6.1"
36+
},
37+
"peerDependencies": {
38+
"redux-thunk": "^1.0.3"
3139
}
3240
}

src/actions/index.js

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import superagent from 'superagent'
2+
import { normalize } from 'normalizr';
3+
import get from 'lodash/get';
4+
5+
import * as actionTypes from '../constants/action-types';
6+
7+
export const requestStart = (url) => {
8+
return {
9+
type: actionTypes.REQUEST_START,
10+
url,
11+
};
12+
};
13+
14+
export const requestSuccess = (url, status, entities) => {
15+
return {
16+
type: actionTypes.REQUEST_SUCCCESS,
17+
url,
18+
status,
19+
entities,
20+
};
21+
};
22+
23+
export const requestFailure = (url, status) => {
24+
return {
25+
type: actionTypes.REQUEST_SUCCCESS,
26+
url,
27+
status,
28+
};
29+
};
30+
31+
export const requestAsync = (url, schema, requestsSelector) => (dispatch, getState) => {
32+
const state = getState();
33+
const requests = requestsSelector(state);
34+
const request = requests[url];
35+
const isPending = get(request, ['isPending'], false);
36+
37+
if (!isPending) {
38+
dispatch(requestStart(url));
39+
40+
superagent.get(url)
41+
.end((err, response) => {
42+
if (err) {
43+
dispatch(requestFailure(url, response.status));
44+
} else {
45+
const normalized = normalize(response.body, schema);
46+
dispatch(requestSuccess(url, response.status, normalized.entities));
47+
}
48+
});
49+
}
50+
};

src/components/create-container.js

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import React, { Component } from 'react';
2+
import storeShape from 'react-redux/lib/utils/storeShape';
3+
4+
import { requestAsync } from '../actions';
5+
6+
const createContainer = (mapPropsToDeps, responsesSelector) => (WrappedComponent) => {
7+
class ReduxQueryContainer extends Component {
8+
componentDidMount() {
9+
this.fetch();
10+
}
11+
12+
componentWillUpdate() {
13+
this.fetch();
14+
}
15+
16+
fetch() {
17+
const { dispatch } = this.context.store;
18+
const deps = mapPropsToDeps(this.props);
19+
dispatch(requestAsync(deps.url, deps.schema, responsesSelector));
20+
}
21+
22+
render() {
23+
return (
24+
<WrappedComponent
25+
{...this.props}
26+
/>
27+
);
28+
}
29+
}
30+
31+
ReduxQueryContainer.displayName = `ReduxQueryContainer(${WrappedComponent.displayName})`;
32+
ReduxQueryContainer.contextTypes = {
33+
store: storeShape,
34+
};
35+
36+
return ReduxQueryContainer;
37+
};
38+
39+
export default createContainer;

src/constants/action-types.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export const REQUEST_START = '@@query/REQUEST_START';
2+
export const REQUEST_SUCCCESS = '@@query/REQUEST_SUCCCESS';
3+
export const REQUEST_FAILURE = '@@query/REQUEST_FAILURE';

src/index.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export { Schema, arrayOf } from 'normalizr';
2+
export { default as createContainer } from './components/create-container';
3+
export { default as requestsReducer } from './reducers/requests';

src/reducers/requests.js

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import values from 'lodash/values';
2+
import includes from 'lodash/includes';
3+
4+
import * as actionTypes from '../constants/action-types';
5+
6+
const request = (state = { status: null, isPending: false }, action) => {
7+
return {
8+
...state,
9+
status: action.status || state.status,
10+
isPending: action.type === actionTypes.REQUEST_START,
11+
};
12+
};
13+
14+
const requests = (state = {}, action) => {
15+
if (includes(values(actionTypes), action.type)) {
16+
return {
17+
...state,
18+
[action.url]: request(state[action.url], action),
19+
};
20+
} else {
21+
return state;
22+
}
23+
};
24+
25+
export default requests;

0 commit comments

Comments
 (0)