-
-
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 #1403 from xulien/master
Replace universal example with async-with-routing and async-universal
- Loading branch information
Showing
28 changed files
with
1,000 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 @@ | ||
{ | ||
"presets": ["es2015", "react"] | ||
} |
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,21 @@ | ||
import 'babel-polyfill' | ||
import React from 'react' | ||
import { render } from 'react-dom' | ||
import { Provider } from 'react-redux' | ||
import { Router, browserHistory } from 'react-router' | ||
|
||
import configureStore from '../common/store/configureStore' | ||
import routes from '../common/routes' | ||
|
||
const initialState = window.__INITIAL_STATE__ | ||
const store = configureStore(initialState) | ||
const rootElement = document.getElementById('app') | ||
|
||
render( | ||
<Provider store={store}> | ||
<Router history={browserHistory}> | ||
{routes} | ||
</Router> | ||
</Provider>, | ||
rootElement | ||
) |
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.map(child => child.data), | ||
receivedAt: Date.now() | ||
} | ||
} | ||
|
||
function fetchPosts(reddit) { | ||
return dispatch => { | ||
dispatch(requestPosts(reddit)) | ||
return fetch(`https://www.reddit.com/r/${reddit}.json`) | ||
.then(response => response.json()) | ||
.then(json => dispatch(receivePosts(reddit, json))) | ||
} | ||
} | ||
|
||
function shouldFetchPosts(state, reddit) { | ||
const posts = state.postsByReddit[reddit] | ||
if (!posts) { | ||
return true | ||
} | ||
if (posts.isFetching) { | ||
return false | ||
} | ||
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,16 @@ | ||
import React, { Component } from 'react' | ||
|
||
export default class App extends Component { | ||
render() { | ||
return ( | ||
<div> | ||
<div> | ||
<h3>Redux async universal example</h3> | ||
<p>Code on <a href="https://github.com/reactjs/redux">Github</a></p> | ||
<hr/> | ||
</div> | ||
{this.props.children} | ||
</div> | ||
) | ||
} | ||
} |
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) ? value : 'Select a subreddit below'}</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.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,115 @@ | ||
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 Reddit extends Component { | ||
|
||
constructor(props) { | ||
super(props) | ||
this.handleChange = this.handleChange.bind(this) | ||
this.handleRefreshClick = this.handleRefreshClick.bind(this) | ||
} | ||
|
||
componentWillReceiveProps(nextProps) { | ||
const { dispatch, params } = this.props | ||
|
||
if (nextProps.params.id !== params.id) { | ||
dispatch(selectReddit(nextProps.params.id)) | ||
if (nextProps.params.id) { | ||
dispatch(fetchPostsIfNeeded(nextProps.params.id)) | ||
} | ||
} | ||
|
||
} | ||
|
||
handleChange(nextReddit) { | ||
this.context.router.push(`/${nextReddit}`) | ||
} | ||
|
||
handleRefreshClick(e) { | ||
e.preventDefault() | ||
|
||
const { dispatch, selectedReddit } = this.props | ||
dispatch(invalidateReddit(selectedReddit)) | ||
dispatch(fetchPostsIfNeeded(selectedReddit)) | ||
} | ||
|
||
render() { | ||
const { selectedReddit, posts, isFetching, lastUpdated } = this.props | ||
const isEmpty = posts.length === 0 | ||
return ( | ||
<div> | ||
<Picker value={selectedReddit} | ||
onChange={this.handleChange} | ||
options={ [ '', 'reactjs', 'frontend' ] } /> | ||
<p> | ||
{lastUpdated && | ||
<span> | ||
Last updated at {new Date(lastUpdated).toLocaleTimeString()}. | ||
{' '} | ||
</span> | ||
} | ||
{!isFetching && selectedReddit && | ||
<a href="#" | ||
onClick={this.handleRefreshClick}> | ||
Refresh | ||
</a> | ||
} | ||
</p> | ||
{isEmpty | ||
? (isFetching ? <h2>Loading...</h2> : <h2>Empty.</h2>) | ||
: <div style={{ opacity: isFetching ? 0.5 : 1 }}> | ||
<Posts posts={posts}/> | ||
</div> | ||
} | ||
</div> | ||
) | ||
} | ||
} | ||
|
||
Reddit.fetchData = (dispatch, params) => { | ||
const subreddit = params.id | ||
if (subreddit) { | ||
return Promise.all([ | ||
dispatch(selectReddit(subreddit)), | ||
dispatch(fetchPostsIfNeeded(subreddit)) | ||
]) | ||
} else { | ||
return Promise.resolve() | ||
} | ||
} | ||
|
||
Reddit.contextTypes = { | ||
router: PropTypes.object | ||
} | ||
|
||
Reddit.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: false, | ||
items: [] | ||
} | ||
|
||
return { | ||
selectedReddit, | ||
posts, | ||
isFetching, | ||
lastUpdated | ||
} | ||
} | ||
|
||
export default connect(mapStateToProps)(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,61 @@ | ||
import { combineReducers } from 'redux' | ||
import { | ||
SELECT_REDDIT, INVALIDATE_REDDIT, | ||
REQUEST_POSTS, RECEIVE_POSTS | ||
} from '../actions' | ||
|
||
function selectedReddit(state = '', 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,13 @@ | ||
import React from 'react' | ||
import Route from 'react-router/lib/Route' | ||
import IndexRoute from 'react-router/lib/IndexRoute' | ||
|
||
import App from './components/App' | ||
import Reddit from './containers/Reddit' | ||
|
||
export default ( | ||
<Route path="/" component={App}> | ||
<IndexRoute component={Reddit}/> | ||
<Route path=":id" component={Reddit}/> | ||
</Route> | ||
) |
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,22 @@ | ||
import { createStore, applyMiddleware } from 'redux' | ||
import thunkMiddleware from 'redux-thunk' | ||
import createLogger from 'redux-logger' | ||
import rootReducer from '../reducers' | ||
|
||
export default function configureStore(initialState) { | ||
const store = createStore( | ||
rootReducer, | ||
initialState, | ||
applyMiddleware(thunkMiddleware, createLogger()) | ||
) | ||
|
||
if (module.hot) { | ||
// Enable Webpack hot module replacement for reducers | ||
module.hot.accept('../reducers', () => { | ||
const nextRootReducer = require('../reducers').default | ||
store.replaceReducer(nextRootReducer) | ||
}) | ||
} | ||
|
||
return store | ||
} |
Oops, something went wrong.