Skip to content

Commit f94eb00

Browse files
committed
all things
1 parent 1f94170 commit f94eb00

15 files changed

+448
-0
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,3 +25,5 @@ build/Release
2525
# Dependency directory
2626
# https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git
2727
node_modules
28+
typings
29+
dist

index.html

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
<!doctype html>
2+
<html>
3+
4+
<head>
5+
<meta charset="UTF-8">
6+
<title>learn redux</title>
7+
</head>
8+
9+
<body>
10+
<div id="root"></div>
11+
<script src="/static/bundle.js"></script>
12+
</body>
13+
14+
</html>

jsconfig.json

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"compilerOptions": {
3+
"target": "ES6",
4+
"module": "commonjs"
5+
}
6+
7+
}

package.json

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
{
2+
"name": "learn-redux-async-action",
3+
"version": "1.0.0",
4+
"description": "",
5+
"main": "app.js",
6+
"scripts": {
7+
"start": "node server.js"
8+
},
9+
"repository": {
10+
"type": "git",
11+
"url": "git+https://github.com/zestxjest/learn-redux-async-action.git"
12+
},
13+
"author": "Dean Zhao",
14+
"license": "MIT",
15+
"bugs": {
16+
"url": "https://github.com/zestxjest/learn-redux-async-action/issues"
17+
},
18+
"homepage": "https://github.com/zestxjest/learn-redux-async-action#readme",
19+
"dependencies": {
20+
"express": "^4.13.3",
21+
"isomorphic-fetch": "^2.2.0",
22+
"react": "^0.14.3",
23+
"react-redux": "^4.0.0",
24+
"redux": "^3.0.4",
25+
"redux-logger": "^2.2.1",
26+
"redux-thunk": "^1.0.0"
27+
},
28+
"devDependencies": {
29+
"babel-core": "^6.3.15",
30+
"babel-loader": "^6.2.0",
31+
"babel-plugin-react-transform": "^1.1.1",
32+
"babel-polyfill": "^6.3.14",
33+
"babel-preset-es2015": "^6.3.13",
34+
"babel-preset-react": "^6.3.13",
35+
"babel-runtime": "^6.3.13",
36+
"react-dom": "^0.14.3",
37+
"react-transform-hmr": "^1.0.1",
38+
"redux-devtools": "^2.1.5",
39+
"webpack": "^1.12.9",
40+
"webpack-dev-middleware": "^1.4.0",
41+
"webpack-hot-middleware": "^2.6.0"
42+
}
43+
}

server.js

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
var webpack = require('webpack')
2+
var webpackDevMiddleware = require('webpack-dev-middleware')
3+
var webpackHotMiddleware = require('webpack-hot-middleware')
4+
var config = require('./webpack.config')
5+
6+
var app = new (require('express'))()
7+
var port = 3000
8+
9+
var compiler = webpack(config)
10+
app.use(webpackDevMiddleware(compiler, { noInfo: true, publicPath: config.output.publicPath }))
11+
app.use(webpackHotMiddleware(compiler))
12+
13+
app.get("/", function(req, res) {
14+
res.sendFile(__dirname + '/index.html')
15+
})
16+
17+
app.listen(port, function(error) {
18+
if (error) {
19+
console.error(error)
20+
} else {
21+
console.info("==> 🌎 Listening on port %s. Open up http://localhost:%s/ in your browser.", port, port)
22+
}
23+
})

src/actions/actions.js

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
import fetch from 'isomorphic-fetch';
2+
export const REQUEST_POSTS = 'REQUEST_POSTS';
3+
export const RECEIVE_POSTS = 'RECEIVE_POSTS';
4+
export const SELECT_REDDIT = 'SELECT_REDDIT';
5+
export const INVALIDATE_REDDIT = 'INVALIDATE_REDDIT';
6+
7+
export function selectReddit(reddit) {
8+
return {
9+
type: SELECT_REDDIT,
10+
reddit
11+
}
12+
}
13+
14+
export function invalidateReddit(reddit) {
15+
return {
16+
type: INVALIDATE_REDDIT,
17+
reddit
18+
}
19+
}
20+
21+
function requestPosts(reddit) {
22+
return {
23+
type: REQUEST_POSTS,
24+
reddit
25+
}
26+
}
27+
28+
function receivePosts(reddit, json) {
29+
return {
30+
type: RECEIVE_POSTS,
31+
reddit,
32+
posts: json.data.children.map(child => child.data),
33+
receivedAt: Date.now()
34+
}
35+
}
36+
37+
38+
function fetchPosts(reddit) {
39+
return dispatch=> {
40+
dispatch(requestPosts(reddit));
41+
return fetch(`https://www.reddit.com/r/${reddit}.json`)
42+
.then(req => req.json())
43+
.then(json => dispatch(receivePosts(reddit, json)));
44+
}
45+
}
46+
47+
function shouldFetchPosts(state, reddit) {
48+
const posts = state.postsByReddit[reddit];
49+
if (!posts) {
50+
return true;
51+
} else if (posts.isFetching) {
52+
return false;
53+
} else {
54+
return posts.didInvalidate;
55+
}
56+
}
57+
58+
export function fetchPostsIfNeeded(reddit) {
59+
return (dispatch, getState) => {
60+
if (shouldFetchPosts(getState(), reddit)) {
61+
return dispatch(fetchPosts(reddit));
62+
}
63+
};
64+
}

src/components/Picker.js

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

src/components/Posts.js

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

src/configureStore.js

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import { createStore, applyMiddleware } from 'redux';
2+
import thunkMiddleware from 'redux-thunk';
3+
import createLogger from 'redux-logger';
4+
import rootReducer from './reducers/reducers';
5+
6+
const loggerMiddleware = createLogger()
7+
8+
const createStoreWithMiddleware = applyMiddleware(
9+
thunkMiddleware,
10+
loggerMiddleware
11+
)(createStore);
12+
13+
export default function configureStore(initialState) {
14+
return createStoreWithMiddleware(rootReducer, initialState);
15+
}

src/containers/AsyncApp.js

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
import React, { Component, PropTypes } from 'react'
2+
import { connect } from 'react-redux'
3+
import { selectReddit, fetchPostsIfNeeded, invalidateReddit } from '../actions/actions'
4+
import Picker from '../components/Picker'
5+
import Posts from '../components/Posts'
6+
7+
class AsyncApp extends Component {
8+
constructor(props) {
9+
super(props)
10+
this.handleChange = this.handleChange.bind(this)
11+
this.handleRefreshClick = this.handleRefreshClick.bind(this)
12+
}
13+
14+
componentDidMount() {
15+
const { dispatch, selectedReddit } = this.props
16+
dispatch(fetchPostsIfNeeded(selectedReddit))
17+
}
18+
19+
componentWillReceiveProps(nextProps) {
20+
if (nextProps.selectedReddit !== this.props.selectedReddit) {
21+
const { dispatch, selectedReddit } = nextProps
22+
dispatch(fetchPostsIfNeeded(selectedReddit))
23+
}
24+
}
25+
26+
handleChange(nextReddit) {
27+
this.props.dispatch(selectReddit(nextReddit))
28+
}
29+
30+
handleRefreshClick(e) {
31+
e.preventDefault()
32+
33+
const { dispatch, selectedReddit } = this.props
34+
dispatch(invalidateReddit(selectedReddit))
35+
dispatch(fetchPostsIfNeeded(selectedReddit))
36+
}
37+
38+
render() {
39+
const { selectedReddit, posts, isFetching, lastUpdated } = this.props
40+
return (
41+
<div>
42+
<Picker value={selectedReddit}
43+
onChange={this.handleChange}
44+
options={[ 'reactjs', 'frontend' ]} />
45+
<p>
46+
{lastUpdated &&
47+
<span>
48+
Last updated at {new Date(lastUpdated).toLocaleTimeString()}.
49+
{' '}
50+
</span>
51+
}
52+
{!isFetching &&
53+
<a href='#'
54+
onClick={this.handleRefreshClick}>
55+
Refresh
56+
</a>
57+
}
58+
</p>
59+
{isFetching && posts.length === 0 &&
60+
<h2>Loading...</h2>
61+
}
62+
{!isFetching && posts.length === 0 &&
63+
<h2>Empty.</h2>
64+
}
65+
{posts.length > 0 &&
66+
<div style={{ opacity: isFetching ? 0.5 : 1 }}>
67+
<Posts posts={posts} />
68+
</div>
69+
}
70+
</div>
71+
)
72+
}
73+
}
74+
75+
AsyncApp.propTypes = {
76+
selectedReddit: PropTypes.string.isRequired,
77+
posts: PropTypes.array.isRequired,
78+
isFetching: PropTypes.bool.isRequired,
79+
lastUpdated: PropTypes.number,
80+
dispatch: PropTypes.func.isRequired
81+
}
82+
83+
function mapStateToProps(state) {
84+
const { selectedReddit, postsByReddit } = state
85+
const {
86+
isFetching,
87+
lastUpdated,
88+
items: posts
89+
} = postsByReddit[selectedReddit] || {
90+
isFetching: true,
91+
items: []
92+
}
93+
94+
return {
95+
selectedReddit,
96+
posts,
97+
isFetching,
98+
lastUpdated
99+
}
100+
}
101+
102+
export default connect(mapStateToProps)(AsyncApp)

src/containers/Root.js

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import React, { Component } from 'react';
2+
import { Provider } from 'react-redux';
3+
import configureStore from '../configureStore';
4+
import AsyncApp from './AsyncApp';
5+
6+
const store = configureStore()
7+
8+
export default class Root extends Component {
9+
render() {
10+
return (
11+
<Provider store={ store } >
12+
<AsyncApp />
13+
< /Provider>
14+
);
15+
}
16+
}

src/index.js

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import 'babel-polyfill';
2+
import React from 'react';
3+
import {render} from 'react-dom';
4+
import Root from './containers/Root'
5+
6+
render(
7+
<Root />,
8+
document.getElementById('root')
9+
)

0 commit comments

Comments
 (0)