diff --git a/examples/async-universal/.babelrc b/examples/async-universal/.babelrc new file mode 100644 index 0000000000..86c445f545 --- /dev/null +++ b/examples/async-universal/.babelrc @@ -0,0 +1,3 @@ +{ + "presets": ["es2015", "react"] +} diff --git a/examples/async-universal/client/index.js b/examples/async-universal/client/index.js new file mode 100644 index 0000000000..b108676ce2 --- /dev/null +++ b/examples/async-universal/client/index.js @@ -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( + + + {routes} + + , + rootElement +) diff --git a/examples/async-universal/common/actions/index.js b/examples/async-universal/common/actions/index.js new file mode 100644 index 0000000000..b3c32cd68d --- /dev/null +++ b/examples/async-universal/common/actions/index.js @@ -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)) + } + } +} diff --git a/examples/async-universal/common/components/App.js b/examples/async-universal/common/components/App.js new file mode 100644 index 0000000000..38b41083cc --- /dev/null +++ b/examples/async-universal/common/components/App.js @@ -0,0 +1,16 @@ +import React, { Component } from 'react' + +export default class App extends Component { + render() { + return ( +
+
+

Redux async universal example

+

Code on Github

+
+
+ {this.props.children} +
+ ) + } +} diff --git a/examples/async-universal/common/components/Picker.js b/examples/async-universal/common/components/Picker.js new file mode 100644 index 0000000000..2f469af982 --- /dev/null +++ b/examples/async-universal/common/components/Picker.js @@ -0,0 +1,29 @@ +import React, { Component, PropTypes } from 'react' + +export default class Picker extends Component { + render() { + const { value, onChange, options } = this.props + + return ( + +

{(value) ? value : 'Select a subreddit below'}

+ +
+ ) + } +} + +Picker.propTypes = { + options: PropTypes.arrayOf( + PropTypes.string.isRequired + ).isRequired, + value: PropTypes.string.isRequired, + onChange: PropTypes.func.isRequired +} diff --git a/examples/async-universal/common/components/Posts.js b/examples/async-universal/common/components/Posts.js new file mode 100644 index 0000000000..dd3285dab9 --- /dev/null +++ b/examples/async-universal/common/components/Posts.js @@ -0,0 +1,17 @@ +import React, { PropTypes, Component } from 'react' + +export default class Posts extends Component { + render() { + return ( + + ) + } +} + +Posts.propTypes = { + posts: PropTypes.array.isRequired +} diff --git a/examples/async-universal/common/containers/Reddit.js b/examples/async-universal/common/containers/Reddit.js new file mode 100644 index 0000000000..c678a0b4c2 --- /dev/null +++ b/examples/async-universal/common/containers/Reddit.js @@ -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 ( +
+ +

+ {lastUpdated && + + Last updated at {new Date(lastUpdated).toLocaleTimeString()}. + {' '} + + } + {!isFetching && selectedReddit && + + Refresh + + } +

+ {isEmpty + ? (isFetching ?

Loading...

:

Empty.

) + :
+ +
+ } +
+ ) + } +} + +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) diff --git a/examples/async-universal/common/reducers/index.js b/examples/async-universal/common/reducers/index.js new file mode 100644 index 0000000000..d6836ee0ff --- /dev/null +++ b/examples/async-universal/common/reducers/index.js @@ -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 diff --git a/examples/async-universal/common/routes.js b/examples/async-universal/common/routes.js new file mode 100644 index 0000000000..0d8bb3717c --- /dev/null +++ b/examples/async-universal/common/routes.js @@ -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 ( + + + + +) diff --git a/examples/async-universal/common/store/configureStore.js b/examples/async-universal/common/store/configureStore.js new file mode 100644 index 0000000000..465d94919a --- /dev/null +++ b/examples/async-universal/common/store/configureStore.js @@ -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 +} diff --git a/examples/async-universal/package.json b/examples/async-universal/package.json new file mode 100644 index 0000000000..123579a793 --- /dev/null +++ b/examples/async-universal/package.json @@ -0,0 +1,52 @@ +{ + "name": "redux-async-universal-example", + "version": "0.0.0", + "description": "Redux async universal example", + "scripts": { + "start": "node server/index.js" + }, + "repository": { + "type": "git", + "url": "https://github.com/reactjs/redux.git" + }, + "keywords": [ + "react", + "reactjs", + "hot", + "reload", + "hmr", + "live", + "edit", + "webpack", + "flux" + ], + "license": "MIT", + "bugs": { + "url": "https://github.com/reactjs/redux/issues" + }, + "homepage": "http://redux.js.org", + "dependencies": { + "babel-polyfill": "^6.3.14", + "express": "^4.13.4", + "isomorphic-fetch": "^2.1.1", + "react": "^0.14.7", + "react-dom": "^0.14.7", + "react-redux": "^4.2.1", + "react-router": "^2.0.0", + "redux": "^3.2.1", + "redux-logger": "^2.4.0", + "redux-thunk": "^1.0.3" + }, + "devDependencies": { + "babel-core": "^6.3.15", + "babel-loader": "^6.2.0", + "babel-preset-es2015": "^6.3.13", + "babel-preset-react": "^6.3.13", + "expect": "^1.6.0", + "node-libs-browser": "^0.5.2", + "react-transform-hmr": "^1.0.4", + "webpack": "^1.9.11", + "webpack-dev-middleware": "^1.2.0", + "webpack-hot-middleware": "^2.2.0" + } +} diff --git a/examples/async-universal/server/index.js b/examples/async-universal/server/index.js new file mode 100644 index 0000000000..04171d26bf --- /dev/null +++ b/examples/async-universal/server/index.js @@ -0,0 +1,2 @@ +require('babel-register') +require('./server') diff --git a/examples/async-universal/server/server.js b/examples/async-universal/server/server.js new file mode 100644 index 0000000000..7d00807214 --- /dev/null +++ b/examples/async-universal/server/server.js @@ -0,0 +1,80 @@ +var webpack = require('webpack') +var webpackDevMiddleware = require('webpack-dev-middleware') +var webpackHotMiddleware = require('webpack-hot-middleware') +var config = require('../webpack.config') +var React = require('react') +var renderToString = require('react-dom/server').renderToString +var Provider = require('react-redux').Provider +var match = require('react-router/lib/match') +var RouterContext = require('react-router/lib/RouterContext') + +var configureStore = require('../common/store/configureStore').default +var routes = require('../common/routes').default + +var app = new (require('express'))() +var port = 3000 + +var compiler = webpack(config) +app.use(webpackDevMiddleware(compiler, { noInfo: true, publicPath: config.output.publicPath })) +app.use(webpackHotMiddleware(compiler)) + +app.use(handleRender) + +function handleRender(req, res) { + match({ routes, location: req.url }, function(error, redirectLocation, renderProps) { + if (error) { + res.status(500).send(error.message) + } else if (redirectLocation) { + res.redirect(302, redirectLocation.pathname + redirectLocation.search) + } else if (renderProps) { + // Create a new Redux store instance + var store = configureStore() + + // Grab static fetchData + var fetchData = renderProps.components[ renderProps.components.length - 1 ].fetchData + + // Query our API asynchronously + fetchData(store.dispatch, renderProps.params).then(() => { + + const html = renderToString( + + + + ) + + var finalState = store.getState() + + res.status(200).send(renderFullPage(html, finalState)) + }) + + } else { + res.status(404).send('Not found') + } + }) +} + +function renderFullPage(html, initialState) { + return ` + + + + Redux Async Universal Example + + +
${html}
+ + + + + ` +} + +app.listen(port, function(error) { + if (error) { + console.error(error) + } else { + console.info("==> 🌎 Listening on port %s. Open up http://localhost:%s/ in your browser.", port, port) + } +}) diff --git a/examples/async-universal/webpack.config.js b/examples/async-universal/webpack.config.js new file mode 100644 index 0000000000..5aaa90d519 --- /dev/null +++ b/examples/async-universal/webpack.config.js @@ -0,0 +1,29 @@ +var path = require('path') +var webpack = require('webpack') + +module.exports = { + devtool: 'cheap-module-eval-source-map', + entry: [ + 'webpack-hot-middleware/client', + './client/index.js' + ], + output: { + path: path.join(__dirname, 'dist'), + filename: 'bundle.js', + publicPath: '/static/' + }, + plugins: [ + new webpack.optimize.OccurenceOrderPlugin(), + new webpack.HotModuleReplacementPlugin() + ], + module: { + loaders: [ + { + test: /\.js$/, + loaders: ['babel'], + exclude: /node_modules/, + include: __dirname + } + ] + } +} \ No newline at end of file diff --git a/examples/async-with-routing/.babelrc b/examples/async-with-routing/.babelrc new file mode 100644 index 0000000000..d0962f5695 --- /dev/null +++ b/examples/async-with-routing/.babelrc @@ -0,0 +1,8 @@ +{ + "presets": ["es2015", "react"], + "env": { + "development": { + "presets": ["react-hmre"] + } + } +} diff --git a/examples/async-with-routing/actions/index.js b/examples/async-with-routing/actions/index.js new file mode 100644 index 0000000000..b3c32cd68d --- /dev/null +++ b/examples/async-with-routing/actions/index.js @@ -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)) + } + } +} diff --git a/examples/async-with-routing/components/App.js b/examples/async-with-routing/components/App.js new file mode 100644 index 0000000000..a054acff6c --- /dev/null +++ b/examples/async-with-routing/components/App.js @@ -0,0 +1,16 @@ +import React, { Component } from 'react' + +export default class App extends Component { + render() { + return ( +
+
+

Redux async with router example

+

Code on Github

+
+
+ {this.props.children} +
+ ) + } +} diff --git a/examples/async-with-routing/components/Picker.js b/examples/async-with-routing/components/Picker.js new file mode 100644 index 0000000000..2f469af982 --- /dev/null +++ b/examples/async-with-routing/components/Picker.js @@ -0,0 +1,29 @@ +import React, { Component, PropTypes } from 'react' + +export default class Picker extends Component { + render() { + const { value, onChange, options } = this.props + + return ( + +

{(value) ? value : 'Select a subreddit below'}

+ +
+ ) + } +} + +Picker.propTypes = { + options: PropTypes.arrayOf( + PropTypes.string.isRequired + ).isRequired, + value: PropTypes.string.isRequired, + onChange: PropTypes.func.isRequired +} diff --git a/examples/async-with-routing/components/Posts.js b/examples/async-with-routing/components/Posts.js new file mode 100644 index 0000000000..dd3285dab9 --- /dev/null +++ b/examples/async-with-routing/components/Posts.js @@ -0,0 +1,17 @@ +import React, { PropTypes, Component } from 'react' + +export default class Posts extends Component { + render() { + return ( + + ) + } +} + +Posts.propTypes = { + posts: PropTypes.array.isRequired +} diff --git a/examples/async-with-routing/containers/Reddit.js b/examples/async-with-routing/containers/Reddit.js new file mode 100644 index 0000000000..8cff005988 --- /dev/null +++ b/examples/async-with-routing/containers/Reddit.js @@ -0,0 +1,112 @@ +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) + } + + componentDidMount() { + const { dispatch, params } = this.props + if (params.id) { + dispatch(selectReddit(params.id)) + dispatch(fetchPostsIfNeeded(params.id)) + } + } + + 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 ( +
+ +

+ {lastUpdated && + + Last updated at {new Date(lastUpdated).toLocaleTimeString()}. + {' '} + + } + {!isFetching && selectedReddit && + + Refresh + + } +

+ {isEmpty + ? (isFetching ?

Loading...

:

Empty.

) + :
+ +
+ } +
+ ) + } +} + +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) diff --git a/examples/async-with-routing/index.html b/examples/async-with-routing/index.html new file mode 100644 index 0000000000..b9b31b6ff1 --- /dev/null +++ b/examples/async-with-routing/index.html @@ -0,0 +1,11 @@ + + + + Redux async with routing example + + +
+
+ + + diff --git a/examples/async-with-routing/index.js b/examples/async-with-routing/index.js new file mode 100644 index 0000000000..2c8735b9b7 --- /dev/null +++ b/examples/async-with-routing/index.js @@ -0,0 +1,19 @@ +import 'babel-polyfill' +import React from 'react' +import { render } from 'react-dom' +import Router from 'react-router/lib/Router' +import browserHistory from 'react-router/lib/browserHistory' +import { Provider } from 'react-redux' +import routes from './routes' +import configureStore from './store/configureStore' + +const store = configureStore() + +render( + + + {routes} + + , + document.getElementById('root') +) diff --git a/examples/async-with-routing/package.json b/examples/async-with-routing/package.json new file mode 100644 index 0000000000..805503889b --- /dev/null +++ b/examples/async-with-routing/package.json @@ -0,0 +1,52 @@ +{ + "name": "redux-async-with-routing-example", + "version": "0.0.0", + "description": "Redux async with routing example", + "scripts": { + "start": "node server.js" + }, + "repository": { + "type": "git", + "url": "https://github.com/reactjs/redux.git" + }, + "keywords": [ + "react", + "reactjs", + "hot", + "reload", + "hmr", + "live", + "edit", + "webpack", + "flux" + ], + "license": "MIT", + "bugs": { + "url": "https://github.com/reactjs/redux/issues" + }, + "homepage": "http://redux.js.org", + "dependencies": { + "babel-polyfill": "^6.3.14", + "isomorphic-fetch": "^2.1.1", + "react": "^0.14.7", + "react-dom": "^0.14.7", + "react-redux": "^4.2.1", + "react-router": "^2.0.0", + "redux": "^3.2.1", + "redux-logger": "^2.4.0", + "redux-thunk": "^1.0.3" + }, + "devDependencies": { + "babel-core": "^6.3.15", + "babel-loader": "^6.2.0", + "babel-preset-es2015": "^6.3.13", + "babel-preset-react": "^6.3.13", + "babel-preset-react-hmre": "^1.0.1", + "expect": "^1.6.0", + "express": "^4.13.3", + "node-libs-browser": "^0.5.2", + "webpack": "^1.9.11", + "webpack-dev-middleware": "^1.2.0", + "webpack-hot-middleware": "^2.2.0" + } +} diff --git a/examples/async-with-routing/reducers/index.js b/examples/async-with-routing/reducers/index.js new file mode 100644 index 0000000000..d6836ee0ff --- /dev/null +++ b/examples/async-with-routing/reducers/index.js @@ -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 diff --git a/examples/async-with-routing/routes.js b/examples/async-with-routing/routes.js new file mode 100644 index 0000000000..2d1dd4abe8 --- /dev/null +++ b/examples/async-with-routing/routes.js @@ -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( + + + + +) diff --git a/examples/async-with-routing/server.js b/examples/async-with-routing/server.js new file mode 100644 index 0000000000..9aeb674633 --- /dev/null +++ b/examples/async-with-routing/server.js @@ -0,0 +1,23 @@ +var webpack = require('webpack') +var webpackDevMiddleware = require('webpack-dev-middleware') +var webpackHotMiddleware = require('webpack-hot-middleware') +var config = require('./webpack.config') + +var app = new (require('express'))() +var port = 3000 + +var compiler = webpack(config) +app.use(webpackDevMiddleware(compiler, { noInfo: true, publicPath: config.output.publicPath })) +app.use(webpackHotMiddleware(compiler)) + +app.get("*", function(req, res) { + res.sendFile(__dirname + '/index.html') +}) + +app.listen(port, function(error) { + if (error) { + console.error(error) + } else { + console.info("==> 🌎 Listening on port %s. Open up http://localhost:%s/ in your browser.", port, port) + } +}) diff --git a/examples/async-with-routing/store/configureStore.js b/examples/async-with-routing/store/configureStore.js new file mode 100644 index 0000000000..465d94919a --- /dev/null +++ b/examples/async-with-routing/store/configureStore.js @@ -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 +} diff --git a/examples/async-with-routing/webpack.config.js b/examples/async-with-routing/webpack.config.js new file mode 100644 index 0000000000..35062a810d --- /dev/null +++ b/examples/async-with-routing/webpack.config.js @@ -0,0 +1,29 @@ +var path = require('path') +var webpack = require('webpack') + +module.exports = { + devtool: 'cheap-module-eval-source-map', + entry: [ + 'webpack-hot-middleware/client', + './index' + ], + output: { + path: path.join(__dirname, 'dist'), + filename: 'bundle.js', + publicPath: '/static/' + }, + plugins: [ + new webpack.optimize.OccurenceOrderPlugin(), + new webpack.HotModuleReplacementPlugin() + ], + module: { + loaders: [ + { + test: /\.js$/, + loaders: ['babel'], + exclude: /node_modules/, + include: __dirname + } + ] + } +}