Skip to content

Commit 9cf1bad

Browse files
committed
Merge pull request #1 from artsy/async_example
Add async example
2 parents c91561f + 8845b0b commit 9cf1bad

File tree

16 files changed

+440
-125
lines changed

16 files changed

+440
-125
lines changed

example/.babelrc

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
{
2+
"presets": ["react", "es2015"],
3+
"env": {
4+
"development": {
5+
"plugins": [
6+
["react-transform", {
7+
"transforms": [{
8+
"transform": "react-transform-hmr",
9+
"imports": ["react"],
10+
"locals": ["module"]
11+
}]
12+
}]
13+
]
14+
}
15+
}
16+
}

example/actions/index.js

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
export const REQUEST_POSTS = 'REQUEST_POSTS'
2+
export const RECEIVE_POSTS = 'RECEIVE_POSTS'
3+
export const SELECT_REDDIT = 'SELECT_REDDIT'
4+
export const INVALIDATE_REDDIT = 'INVALIDATE_REDDIT'
5+
6+
export function selectReddit(reddit) {
7+
return {
8+
type: SELECT_REDDIT,
9+
reddit
10+
}
11+
}
12+
13+
export function invalidateReddit(reddit) {
14+
return {
15+
type: INVALIDATE_REDDIT,
16+
reddit
17+
}
18+
}
19+
20+
export function requestPosts(reddit) {
21+
return {
22+
type: REQUEST_POSTS,
23+
reddit
24+
}
25+
}
26+
27+
export function receivePosts(reddit, posts) {
28+
return {
29+
type: RECEIVE_POSTS,
30+
reddit,
31+
posts,
32+
receivedAt: Date.now()
33+
}
34+
}

example/components/Layout.js

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import React, { PropTypes } from 'react'
2+
import Picker from '../components/Picker'
3+
import Posts from '../components/Posts'
4+
5+
export default function Layout(_, { posts, isFetching, lastUpdated, onRefresh }) {
6+
return (
7+
<div>
8+
<Picker options={[ 'reactjs', 'frontend' ]} />
9+
<p>
10+
{lastUpdated &&
11+
<span>
12+
Last updated at {new Date(lastUpdated).toLocaleTimeString()}.
13+
{' '}
14+
</span>
15+
}
16+
{!isFetching &&
17+
<a href="#"
18+
onClick={ e => { e.preventDefault(); onRefresh() } }>
19+
Refresh
20+
</a>
21+
}
22+
</p>
23+
{isFetching && posts.length === 0 &&
24+
<h2>Loading...</h2>
25+
}
26+
{!isFetching && posts.length === 0 &&
27+
<h2>Empty.</h2>
28+
}
29+
{posts.length > 0 &&
30+
<div style={{ opacity: isFetching ? 0.5 : 1 }}>
31+
<Posts />
32+
</div>
33+
}
34+
</div>
35+
)
36+
}
37+
38+
Layout.contextTypes = {
39+
posts: PropTypes.array.isRequired,
40+
isFetching: PropTypes.bool.isRequired,
41+
lastUpdated: PropTypes.number,
42+
onRefresh: PropTypes.func.isRequired
43+
}

example/components/Picker.js

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import React, { PropTypes } from 'react'
2+
3+
export default function Picker({ options }, { selectedReddit, onSelectReddit }) {
4+
return (
5+
<span>
6+
<h1>{ selectedReddit }</h1>
7+
<select onChange={ e => onSelectReddit(e.target.value) }
8+
value={ selectedReddit }>
9+
{ options.map(option =>
10+
<option value={ option } key={ option }>
11+
{ option }
12+
</option>)
13+
}
14+
</select>
15+
</span>
16+
)
17+
}
18+
19+
Picker.propTypes = {
20+
options: PropTypes.arrayOf(
21+
PropTypes.string.isRequired
22+
).isRequired
23+
}
24+
25+
Picker.contextTypes = {
26+
selectedReddit: PropTypes.string.isRequired,
27+
onSelectReddit: PropTypes.func.isRequired
28+
}

example/components/Posts.js

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import React, { PropTypes } from 'react'
2+
3+
export default function Posts(_, { posts }) {
4+
return (
5+
<ul>
6+
{ posts.map((post, i) =>
7+
<li key={ i }>{ post.title }</li>
8+
)}
9+
</ul>
10+
)
11+
}
12+
13+
Posts.contextTypes = {
14+
posts: PropTypes.array.isRequired
15+
}

example/controllers/App.js

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
import 'babel-polyfill'
2+
import fetch from 'isomorphic-fetch'
3+
import React, { Component, PropTypes } from 'react'
4+
import { controller, getProps } from 'react-redux-controller'
5+
import * as actions from '../actions'
6+
import * as selectors from '../selectors'
7+
import Layout from '../components/Layout'
8+
9+
const controllerGenerators = {
10+
*initialize() {
11+
const { selectedReddit } = yield getProps
12+
13+
yield this.fetchPostsIfNeeded(selectedReddit)
14+
},
15+
16+
*onSelectReddit(nextReddit) {
17+
const { dispatch, selectedReddit } = yield getProps
18+
19+
dispatch(actions.selectReddit(nextReddit))
20+
21+
if (nextReddit !== selectedReddit) {
22+
yield this.fetchPostsIfNeeded(nextReddit)
23+
}
24+
},
25+
26+
*onRefresh() {
27+
const { dispatch, selectedReddit } = yield getProps
28+
29+
dispatch(actions.invalidateReddit(selectedReddit))
30+
yield this.fetchPostsIfNeeded(selectedReddit)
31+
},
32+
33+
*fetchPostsIfNeeded(reddit) {
34+
const { postsByReddit } = yield getProps
35+
36+
const posts = postsByReddit[reddit]
37+
if (!posts || !posts.isFetching || posts.didInvalidate) {
38+
yield this.fetchPosts(reddit)
39+
}
40+
},
41+
42+
*fetchPosts(reddit) {
43+
const { dispatch } = yield getProps
44+
45+
dispatch(actions.requestPosts(reddit))
46+
const response = yield fetch(`http://www.reddit.com/r/${reddit}.json`)
47+
const responseJson = yield response.json()
48+
const newPosts = responseJson.data.children.map(child => child.data)
49+
dispatch(actions.receivePosts(reddit, newPosts))
50+
}
51+
}
52+
53+
export default controller(Layout, controllerGenerators, selectors)

example/index.html

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<!DOCTYPE html>
2+
<html>
3+
<head>
4+
<title>Redux async example</title>
5+
</head>
6+
<body>
7+
<div id="root">
8+
</div>
9+
<script src="/static/bundle.js"></script>
10+
</body>
11+
</html>

example/index.js

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import 'babel-polyfill'
2+
import React from 'react'
3+
import { render } from 'react-dom'
4+
import { Provider } from 'react-redux'
5+
import App from './controllers/App'
6+
import configureStore from './store/configureStore'
7+
8+
const store = configureStore()
9+
10+
render(
11+
<Provider store={store}>
12+
<App />
13+
</Provider>,
14+
document.getElementById('root')
15+
)

example/package.json

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
{
2+
"name": "redux-async-example",
3+
"version": "0.0.0",
4+
"description": "Redux async example",
5+
"scripts": {
6+
"start": "node server.js"
7+
},
8+
"repository": {
9+
"type": "git",
10+
"url": "https://github.com/rackt/redux.git"
11+
},
12+
"keywords": [
13+
"react",
14+
"reactjs",
15+
"hot",
16+
"reload",
17+
"hmr",
18+
"live",
19+
"edit",
20+
"webpack",
21+
"flux"
22+
],
23+
"license": "MIT",
24+
"bugs": {
25+
"url": "https://github.com/rackt/redux/issues"
26+
},
27+
"homepage": "http://rackt.github.io/redux",
28+
"dependencies": {
29+
"isomorphic-fetch": "^2.1.1",
30+
"react": "^0.14.0",
31+
"react-dom": "^0.14.0",
32+
"react-redux": "^4.0.0",
33+
"react-redux-controller": "^0.1.1",
34+
"redux": "^3.0.0",
35+
"redux-logger": "^2.0.2",
36+
"redux-thunk": "^0.1.0"
37+
},
38+
"devDependencies": {
39+
"babel-core": "^6.3.21",
40+
"babel-loader": "^6.2.0",
41+
"babel-plugin-react-transform": "^2.0.0-beta1",
42+
"babel-polyfill": "^6.3.14",
43+
"babel-preset-es2015": "^6.3.13",
44+
"babel-preset-react": "^6.3.13",
45+
"expect": "^1.6.0",
46+
"express": "^4.13.3",
47+
"node-libs-browser": "^0.5.2",
48+
"react-transform-hmr": "^1.0.0",
49+
"webpack": "^1.11.0",
50+
"webpack-dev-middleware": "^1.2.0",
51+
"webpack-hot-middleware": "^2.2.0"
52+
}
53+
}

example/reducers/index.js

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
import { combineReducers } from 'redux'
2+
import {
3+
SELECT_REDDIT, INVALIDATE_REDDIT,
4+
REQUEST_POSTS, RECEIVE_POSTS
5+
} from '../actions'
6+
7+
function selectedReddit(state = 'reactjs', action) {
8+
switch (action.type) {
9+
case SELECT_REDDIT:
10+
return action.reddit
11+
default:
12+
return state
13+
}
14+
}
15+
16+
function posts(state = {
17+
isFetching: false,
18+
didInvalidate: false,
19+
items: []
20+
}, action) {
21+
switch (action.type) {
22+
case INVALIDATE_REDDIT:
23+
return Object.assign({}, state, {
24+
didInvalidate: true
25+
})
26+
case REQUEST_POSTS:
27+
return Object.assign({}, state, {
28+
isFetching: true,
29+
didInvalidate: false
30+
})
31+
case RECEIVE_POSTS:
32+
return Object.assign({}, state, {
33+
isFetching: false,
34+
didInvalidate: false,
35+
items: action.posts,
36+
lastUpdated: action.receivedAt
37+
})
38+
default:
39+
return state
40+
}
41+
}
42+
43+
function postsByReddit(state = { }, action) {
44+
switch (action.type) {
45+
case INVALIDATE_REDDIT:
46+
case RECEIVE_POSTS:
47+
case REQUEST_POSTS:
48+
return Object.assign({}, state, {
49+
[action.reddit]: posts(state[action.reddit], action)
50+
})
51+
default:
52+
return state
53+
}
54+
}
55+
56+
const rootReducer = combineReducers({
57+
postsByReddit,
58+
selectedReddit
59+
})
60+
61+
export default rootReducer

0 commit comments

Comments
 (0)