Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Replace universal example with async-with-routing and async-universal #1403

Closed
wants to merge 42 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
3ddb19f
basic universal example with react-router and react-router-redux
Feb 15, 2016
c8ab126
cleaning
Feb 15, 2016
bf0fafb
update to react-router-redux 4.x
Feb 15, 2016
46dd120
cleaning
Feb 15, 2016
6426b3f
cleaning
Feb 15, 2016
f7ec1a9
init
Feb 17, 2016
e50bcad
async-router example
Feb 17, 2016
0fd5f12
async-with-routing example
Feb 17, 2016
7393f90
cleaning
Feb 17, 2016
81b8006
cleaning
Feb 17, 2016
40f6ec6
delete universal-with-router based on counter example
Feb 17, 2016
25cfc5a
cleaning
Feb 17, 2016
41932f1
simplification of the routing
Feb 17, 2016
4dc3944
title update
Feb 17, 2016
53a594b
linting
Feb 17, 2016
2c42ba8
linting
Feb 18, 2016
d05f8d2
name and description update
Feb 18, 2016
0dc353f
async-universal example
Feb 18, 2016
30e4d80
generic index route
Feb 23, 2016
4033f7d
cleaning
Feb 23, 2016
35fcf27
generic index route
Feb 23, 2016
c91dccb
Layout.js removed
Mar 11, 2016
71d55cf
intermediate refactoring
Mar 11, 2016
9633ecf
cleaning
Mar 11, 2016
c8bfd67
move fetchdata into container
Mar 11, 2016
ad054bb
cleaning
Mar 11, 2016
a1f2d63
remove transform-class-properties
Apr 12, 2016
545b288
remove static keyword
Apr 12, 2016
a489327
missing space
Apr 12, 2016
d4604bb
typo
Apr 12, 2016
110489e
remove unused import
Apr 12, 2016
5b96f84
Remove hacks to alias Redux to src folder in examples
Apr 12, 2016
50ef70c
Per-file ESLint exceptions removed
Apr 12, 2016
bf3b17a
Layout.js renamed to App.js
Apr 12, 2016
6890c4a
Remove NoErrorsPlugin
Apr 12, 2016
38105ca
Remove NoErrorsPlugin
Apr 12, 2016
a424585
compile redux from source is needed here
Apr 12, 2016
6a5d6e6
App component with kind of header added
Apr 12, 2016
a56fcbb
indentation
Apr 12, 2016
2f3cad8
babel-preset-react-hmre removed
Apr 12, 2016
35469d4
es5 version for the sake of consistency
Apr 14, 2016
052c8d7
Merge https://github.com/reactjs/redux
Apr 21, 2016
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions examples/async-universal/.babelrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"presets": ["es2015", "react"]
}
21 changes: 21 additions & 0 deletions examples/async-universal/client/index.js
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
)
64 changes: 64 additions & 0 deletions examples/async-universal/common/actions/index.js
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,
reddit
}
}

export function invalidateReddit(reddit) {
return {
type: INVALIDATE_REDDIT,
reddit
}
}

function requestPosts(reddit) {
return {
type: REQUEST_POSTS,
reddit
}
}

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))
}
}
}
16 changes: 16 additions & 0 deletions examples/async-universal/common/components/App.js
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>
)
}
}
29 changes: 29 additions & 0 deletions examples/async-universal/common/components/Picker.js
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
}
17 changes: 17 additions & 0 deletions examples/async-universal/common/components/Posts.js
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
}
115 changes: 115 additions & 0 deletions examples/async-universal/common/containers/Reddit.js
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)
61 changes: 61 additions & 0 deletions examples/async-universal/common/reducers/index.js
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
13 changes: 13 additions & 0 deletions examples/async-universal/common/routes.js
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}/>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This config is a bit weird. Routes describe component nesting structure. Reddit being inside Reddit doesn’t quite make sense to me. I’m not very good at router configs these days, but I think there should be a way to express this more correctly.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK, I get it now. You used to have Layout but I asked to remove it, and this is why it looks strange now. As I also write below, please add Layout back but make it more useful. You can call it App instead and add a header to it that links to the source, like in real-world example. I believe this should be enough to use what seems to be a more correct route config (with IndexRoute and a Route inside a generic app-wide route).

</Route>
)
22 changes: 22 additions & 0 deletions examples/async-universal/common/store/configureStore.js
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
}
Loading