-
-
Notifications
You must be signed in to change notification settings - Fork 15.2k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #474 from gaearon/add-async-example-2
Add a simple async example
- Loading branch information
Showing
13 changed files
with
429 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
{ | ||
"stage": 2 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,64 @@ | ||
import fetch from 'isomorphic-fetch'; | ||
|
||
export const REQUEST_POSTS = 'REQUEST_POSTS'; | ||
export const RECEIVE_POSTS = 'RECEIVE_POSTS'; | ||
export const SELECT_REDDIT = 'SELECT_REDDIT'; | ||
export const INVALIDATE_REDDIT = 'INVALIDATE_REDDIT'; | ||
|
||
export function selectReddit(reddit) { | ||
return { | ||
type: SELECT_REDDIT, | ||
}; | ||
} | ||
|
||
export function invalidateReddit(reddit) { | ||
return { | ||
type: INVALIDATE_REDDIT, | ||
}; | ||
} | ||
|
||
function requestPosts(reddit) { | ||
return { | ||
type: REQUEST_POSTS, | ||
}; | ||
} | ||
|
||
function receivePosts(reddit, json) { | ||
return { | ||
type: RECEIVE_POSTS, | ||
reddit: reddit, | ||
posts: json.data.children, | ||
receivedAt: Date.now() | ||
}; | ||
} | ||
|
||
function fetchPosts(reddit) { | ||
return dispatch => { | ||
dispatch(requestPosts(reddit)); | ||
return fetch(`http://www.reddit.com/r/${reddit}.json`) | ||
.then(req => req.json()) | ||
.then(json => dispatch(receivePosts(reddit, json))); | ||
} | ||
} | ||
|
||
function shouldFetchPosts(state, reddit) { | ||
const posts = state.postsByReddit[reddit]; | ||
if (!posts) { | ||
return true; | ||
} else if (posts.isFetching) { | ||
return false; | ||
} else { | ||
return posts.didInvalidate; | ||
} | ||
} | ||
|
||
export function fetchPostsIfNeeded(reddit) { | ||
return (dispatch, getState) => { | ||
if (shouldFetchPosts(getState(), reddit)) { | ||
return dispatch(fetchPosts(reddit)); | ||
} | ||
}; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
import React, { Component, PropTypes } from 'react'; | ||
|
||
export default class Picker extends Component { | ||
render () { | ||
const { value, onChange, options } = this.props; | ||
|
||
return ( | ||
<span> | ||
<h1>{value}</h1> | ||
<select onChange={e => onChange(e.target.value)} | ||
value={value}> | ||
{options.map(option => | ||
<option value={option} key={option}> | ||
{option} | ||
</option>) | ||
} | ||
</select> | ||
</span> | ||
); | ||
} | ||
} | ||
|
||
Picker.propTypes = { | ||
options: PropTypes.arrayOf( | ||
PropTypes.string.isRequired | ||
).isRequired, | ||
value: PropTypes.string.isRequired, | ||
onChange: PropTypes.func.isRequired | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
import React, { PropTypes, Component } from 'react'; | ||
|
||
export default class Posts extends Component { | ||
render () { | ||
return ( | ||
<ul> | ||
{this.props.posts.map((post, i) => | ||
<li key={i}>{post.data.title}</li> | ||
)} | ||
</ul> | ||
); | ||
} | ||
} | ||
|
||
Posts.propTypes = { | ||
posts: PropTypes.array.isRequired | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,102 @@ | ||
import React, { Component, PropTypes } from 'react'; | ||
import { connect } from 'react-redux'; | ||
import { selectReddit, fetchPostsIfNeeded, invalidateReddit } from '../actions'; | ||
import Picker from '../components/Picker'; | ||
import Posts from '../components/Posts'; | ||
|
||
class AsyncApp extends Component { | ||
constructor(props) { | ||
super(props); | ||
this.handleChange = this.handleChange.bind(this); | ||
this.handleRefreshClick = this.handleRefreshClick.bind(this); | ||
} | ||
|
||
componentDidMount() { | ||
const { dispatch, selectedReddit } = this.props; | ||
dispatch(fetchPostsIfNeeded(selectedReddit)); | ||
} | ||
|
||
componentWillReceiveProps(nextProps) { | ||
if (nextProps.selectedReddit !== this.props.selectedReddit) { | ||
const { dispatch, selectedReddit } = nextProps; | ||
dispatch(fetchPostsIfNeeded(selectedReddit)); | ||
} | ||
} | ||
|
||
handleChange(nextReddit) { | ||
this.props.dispatch(selectReddit(nextReddit)); | ||
} | ||
|
||
handleRefreshClick(e) { | ||
e.preventDefault(); | ||
|
||
const { dispatch, selectedReddit } = this.props; | ||
dispatch(invalidateReddit(selectedReddit)); | ||
dispatch(fetchPostsIfNeeded(selectedReddit)); | ||
} | ||
|
||
render () { | ||
const { selectedReddit, posts, isFetching, lastUpdated } = this.props; | ||
return ( | ||
<div> | ||
<Picker value={selectedReddit} | ||
onChange={this.handleChange} | ||
options={['reactjs', 'frontend']} /> | ||
<p> | ||
{lastUpdated && | ||
<span> | ||
Last updated at {new Date(lastUpdated).toLocaleTimeString()}. | ||
{' '} | ||
</span> | ||
} | ||
{!isFetching && | ||
<a href='#' | ||
onClick={this.handleRefreshClick}> | ||
Refresh | ||
</a> | ||
} | ||
</p> | ||
{isFetching && posts.length === 0 && | ||
<h2>Loading...</h2> | ||
} | ||
{!isFetching && posts.length === 0 && | ||
<h2>Empty.</h2> | ||
} | ||
{posts.length > 0 && | ||
<div style={{ opacity: isFetching ? 0.5 : 1 }}> | ||
<Posts posts={posts} /> | ||
</div> | ||
} | ||
</div> | ||
); | ||
} | ||
} | ||
|
||
AsyncApp.propTypes = { | ||
selectedReddit: PropTypes.string.isRequired, | ||
posts: PropTypes.array.isRequired, | ||
isFetching: PropTypes.bool.isRequired, | ||
lastUpdated: PropTypes.number, | ||
dispatch: PropTypes.func.isRequired | ||
}; | ||
|
||
function mapStateToProps(state) { | ||
const { selectedReddit, postsByReddit } = state; | ||
const { | ||
isFetching, | ||
lastUpdated, | ||
items: posts | ||
} = postsByReddit[selectedReddit] || { | ||
isFetching: true, | ||
items: [] | ||
}; | ||
|
||
return { | ||
selectedReddit, | ||
posts, | ||
isFetching, | ||
lastUpdated | ||
}; | ||
} | ||
|
||
export default connect(mapStateToProps)(AsyncApp); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
import React, { Component } from 'react'; | ||
import { Provider } from 'react-redux'; | ||
import configureStore from '../store/configureStore'; | ||
import AsyncApp from './AsyncApp'; | ||
|
||
const store = configureStore(); | ||
|
||
export default class Root extends Component { | ||
render() { | ||
return ( | ||
<Provider store={store}> | ||
{() => <AsyncApp />} | ||
</Provider> | ||
); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
<html> | ||
<head> | ||
<title>Redux async example</title> | ||
</head> | ||
<body> | ||
<div id="root"> | ||
</div> | ||
</body> | ||
<script src="/static/bundle.js"></script> | ||
</html> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
import 'babel-core/polyfill'; | ||
|
||
import React from 'react'; | ||
import Root from './containers/Root'; | ||
|
||
React.render( | ||
<Root />, | ||
document.getElementById('root') | ||
); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
{ | ||
"name": "redux-async-example", | ||
"version": "0.0.0", | ||
"description": "Redux async example", | ||
"main": "server.js", | ||
"scripts": { | ||
"start": "node server.js" | ||
}, | ||
"repository": { | ||
"type": "git", | ||
"url": "/~https://github.com/gaearon/redux.git" | ||
}, | ||
"keywords": [ | ||
"react", | ||
"reactjs", | ||
"hot", | ||
"reload", | ||
"hmr", | ||
"live", | ||
"edit", | ||
"webpack", | ||
"flux" | ||
], | ||
"license": "MIT", | ||
"bugs": { | ||
"url": "/~https://github.com/gaearon/redux/issues" | ||
}, | ||
"homepage": "/~https://github.com/gaearon/redux#readme", | ||
"dependencies": { | ||
"isomorphic-fetch": "^2.1.1", | ||
"react": "^0.13.3", | ||
"react-redux": "^0.8.0", | ||
"redux": "^1.0.0-rc", | ||
"redux-logger": "0.0.3", | ||
"redux-thunk": "^0.1.0" | ||
}, | ||
"devDependencies": { | ||
"babel-core": "^5.6.18", | ||
"babel-loader": "^5.1.4", | ||
"expect": "^1.6.0", | ||
"jsdom": "^5.6.1", | ||
"mocha": "^2.2.5", | ||
"mocha-jsdom": "^1.0.0", | ||
"node-libs-browser": "^0.5.2", | ||
"react-hot-loader": "^1.2.8", | ||
"webpack": "^1.9.11", | ||
"webpack-dev-server": "^1.9.0" | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,61 @@ | ||
import { combineReducers } from 'redux'; | ||
import { | ||
SELECT_REDDIT, INVALIDATE_REDDIT, | ||
REQUEST_POSTS, RECEIVE_POSTS | ||
} from '../actions'; | ||
|
||
function selectedReddit(state = 'reactjs', action) { | ||
switch (action.type) { | ||
case SELECT_REDDIT: | ||
return action.reddit; | ||
default: | ||
return state; | ||
} | ||
} | ||
|
||
function posts(state = { | ||
isFetching: false, | ||
didInvalidate: false, | ||
items: [] | ||
}, action) { | ||
switch (action.type) { | ||
case INVALIDATE_REDDIT: | ||
return Object.assign({}, state, { | ||
didInvalidate: true | ||
}); | ||
case REQUEST_POSTS: | ||
return Object.assign({}, state, { | ||
isFetching: true, | ||
didInvalidate: false | ||
}); | ||
case RECEIVE_POSTS: | ||
return Object.assign({}, state, { | ||
isFetching: false, | ||
didInvalidate: false, | ||
items: action.posts, | ||
lastUpdated: action.receivedAt | ||
}); | ||
default: | ||
return state; | ||
} | ||
} | ||
|
||
function postsByReddit(state = { }, action) { | ||
switch (action.type) { | ||
case INVALIDATE_REDDIT: | ||
case RECEIVE_POSTS: | ||
case REQUEST_POSTS: | ||
return Object.assign({}, state, { | ||
[action.reddit]: posts(state[action.reddit], action) | ||
}); | ||
default: | ||
return state; | ||
} | ||
} | ||
|
||
const rootReducer = combineReducers({ | ||
postsByReddit, | ||
selectedReddit | ||
}); | ||
|
||
export default rootReducer; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
var webpack = require('webpack'); | ||
var WebpackDevServer = require('webpack-dev-server'); | ||
var config = require('./webpack.config'); | ||
|
||
new WebpackDevServer(webpack(config), { | ||
publicPath: config.output.publicPath, | ||
hot: true, | ||
historyApiFallback: true, | ||
stats: { | ||
colors: true | ||
} | ||
}).listen(3000, 'localhost', function (err) { | ||
if (err) { | ||
console.log(err); | ||
} | ||
|
||
console.log('Listening at localhost:3000'); | ||
}); |
Oops, something went wrong.