From 3ddb19f1405e34b5eec9febe347825923c27b9c5 Mon Sep 17 00:00:00 2001 From: xulien Date: Mon, 15 Feb 2016 13:22:08 +0100 Subject: [PATCH 01/44] basic universal example with react-router and react-router-redux --- examples/universal-with-router/.babelrc | 3 + .../universal-with-router/client/index.js | 26 +++++ .../common/actions/index.js | 42 +++++++ .../common/api/counter.js | 13 +++ .../common/components/Counter.js | 30 +++++ .../common/components/Home.js | 11 ++ .../common/components/Layout.js | 19 ++++ .../common/components/Sample.js | 11 ++ .../common/containers/Counter.js | 16 +++ .../common/reducers/counter.js | 14 +++ .../common/reducers/index.js | 10 ++ .../universal-with-router/common/routes.js | 15 +++ .../common/store/configureStore.js | 24 ++++ examples/universal-with-router/index.js | 1 + examples/universal-with-router/package.json | 42 +++++++ .../universal-with-router/server/index.js | 2 + .../universal-with-router/server/server.js | 105 ++++++++++++++++++ .../universal-with-router/webpack.config.js | 51 +++++++++ 18 files changed, 435 insertions(+) create mode 100644 examples/universal-with-router/.babelrc create mode 100644 examples/universal-with-router/client/index.js create mode 100644 examples/universal-with-router/common/actions/index.js create mode 100644 examples/universal-with-router/common/api/counter.js create mode 100644 examples/universal-with-router/common/components/Counter.js create mode 100644 examples/universal-with-router/common/components/Home.js create mode 100644 examples/universal-with-router/common/components/Layout.js create mode 100644 examples/universal-with-router/common/components/Sample.js create mode 100644 examples/universal-with-router/common/containers/Counter.js create mode 100644 examples/universal-with-router/common/reducers/counter.js create mode 100644 examples/universal-with-router/common/reducers/index.js create mode 100644 examples/universal-with-router/common/routes.js create mode 100644 examples/universal-with-router/common/store/configureStore.js create mode 100644 examples/universal-with-router/index.js create mode 100644 examples/universal-with-router/package.json create mode 100644 examples/universal-with-router/server/index.js create mode 100644 examples/universal-with-router/server/server.js create mode 100644 examples/universal-with-router/webpack.config.js diff --git a/examples/universal-with-router/.babelrc b/examples/universal-with-router/.babelrc new file mode 100644 index 0000000000..dd87fb5136 --- /dev/null +++ b/examples/universal-with-router/.babelrc @@ -0,0 +1,3 @@ +{ + presets: ["es2015", "react"] +} \ No newline at end of file diff --git a/examples/universal-with-router/client/index.js b/examples/universal-with-router/client/index.js new file mode 100644 index 0000000000..b6565eb326 --- /dev/null +++ b/examples/universal-with-router/client/index.js @@ -0,0 +1,26 @@ +import 'babel-polyfill' +import React from 'react' +import { render } from 'react-dom' +import { Provider } from 'react-redux' +import { Router, Route, browserHistory } from 'react-router' +import { syncHistory } from 'react-router-redux' + +import configureStore from '../common/store/configureStore' +import reducers from '../common/reducers' +import routes from '../common/routes' + +const initialState = window.__INITIAL_STATE__ +const reduxRouterMiddleware = syncHistory(browserHistory) +const store = configureStore(initialState, [reduxRouterMiddleware]) +const rootElement = document.getElementById('app') + +reduxRouterMiddleware.listenForReplays(store) + +render( + + + {routes} + + , + rootElement +) diff --git a/examples/universal-with-router/common/actions/index.js b/examples/universal-with-router/common/actions/index.js new file mode 100644 index 0000000000..cdd84fe881 --- /dev/null +++ b/examples/universal-with-router/common/actions/index.js @@ -0,0 +1,42 @@ +export const SET_COUNTER = 'SET_COUNTER' +export const INCREMENT_COUNTER = 'INCREMENT_COUNTER' +export const DECREMENT_COUNTER = 'DECREMENT_COUNTER' + +export function set(value) { + return { + type: SET_COUNTER, + payload: value + } +} + +export function increment() { + return { + type: INCREMENT_COUNTER + } +} + +export function decrement() { + return { + type: DECREMENT_COUNTER + } +} + +export function incrementIfOdd() { + return (dispatch, getState) => { + const { counter } = getState() + + if (counter % 2 === 0) { + return + } + + dispatch(increment()) + } +} + +export function incrementAsync(delay = 1000) { + return dispatch => { + setTimeout(() => { + dispatch(increment()) + }, delay) + } +} diff --git a/examples/universal-with-router/common/api/counter.js b/examples/universal-with-router/common/api/counter.js new file mode 100644 index 0000000000..2bdd1f3b64 --- /dev/null +++ b/examples/universal-with-router/common/api/counter.js @@ -0,0 +1,13 @@ +function getRandomInt(min, max) { + return Math.floor(Math.random() * (max - min)) + min +} + +export function fetchCounter(callback) { + // Rather than immediately returning, we delay our code with a timeout to simulate asynchronous behavior + setTimeout(() => { + callback(getRandomInt(1, 100)) + }, 500) + + // In the case of a real world API call, you'll normally run into a Promise like this: + // API.getUser().then(user => callback(user)) +} diff --git a/examples/universal-with-router/common/components/Counter.js b/examples/universal-with-router/common/components/Counter.js new file mode 100644 index 0000000000..61532854e4 --- /dev/null +++ b/examples/universal-with-router/common/components/Counter.js @@ -0,0 +1,30 @@ +import React, { Component, PropTypes } from 'react' + +class Counter extends Component { + render() { + const { increment, incrementIfOdd, incrementAsync, decrement, counter } = this.props + return ( +

+ Clicked: {counter} times + {' '} + + {' '} + + {' '} + + {' '} + +

+ ) + } +} + +Counter.propTypes = { + increment: PropTypes.func.isRequired, + incrementIfOdd: PropTypes.func.isRequired, + incrementAsync: PropTypes.func.isRequired, + decrement: PropTypes.func.isRequired, + counter: PropTypes.number.isRequired +} + +export default Counter diff --git a/examples/universal-with-router/common/components/Home.js b/examples/universal-with-router/common/components/Home.js new file mode 100644 index 0000000000..695b97dd18 --- /dev/null +++ b/examples/universal-with-router/common/components/Home.js @@ -0,0 +1,11 @@ +import React, { Component } from 'react' + +export default class Home extends Component { + render() { + return ( +
+

Home page

+
+ ) + } +} diff --git a/examples/universal-with-router/common/components/Layout.js b/examples/universal-with-router/common/components/Layout.js new file mode 100644 index 0000000000..529bc67952 --- /dev/null +++ b/examples/universal-with-router/common/components/Layout.js @@ -0,0 +1,19 @@ +import React, { Component } from 'react' +import { Link } from 'react-router' + +export default class Layout extends Component { + render() { + return ( +
+ + {this.props.children} +
+ ) + } +} diff --git a/examples/universal-with-router/common/components/Sample.js b/examples/universal-with-router/common/components/Sample.js new file mode 100644 index 0000000000..4d50f444bb --- /dev/null +++ b/examples/universal-with-router/common/components/Sample.js @@ -0,0 +1,11 @@ +import React, { Component } from 'react' + +export default class Sample extends Component { + render() { + return ( +
+

Sample

+
+ ) + } +} diff --git a/examples/universal-with-router/common/containers/Counter.js b/examples/universal-with-router/common/containers/Counter.js new file mode 100644 index 0000000000..3a931cece5 --- /dev/null +++ b/examples/universal-with-router/common/containers/Counter.js @@ -0,0 +1,16 @@ +import { bindActionCreators } from 'redux' +import { connect } from 'react-redux' +import Counter from '../components/Counter' +import * as CounterActions from '../actions' + +function mapStateToProps(state) { + return { + counter: state.counter + } +} + +function mapDispatchToProps(dispatch) { + return bindActionCreators(CounterActions, dispatch) +} + +export default connect(mapStateToProps, mapDispatchToProps)(Counter) diff --git a/examples/universal-with-router/common/reducers/counter.js b/examples/universal-with-router/common/reducers/counter.js new file mode 100644 index 0000000000..ba4458db85 --- /dev/null +++ b/examples/universal-with-router/common/reducers/counter.js @@ -0,0 +1,14 @@ +import { SET_COUNTER, INCREMENT_COUNTER, DECREMENT_COUNTER } from '../actions' + +export default function counter(state = 0, action) { + switch (action.type) { + case SET_COUNTER: + return action.payload + case INCREMENT_COUNTER: + return state + 1 + case DECREMENT_COUNTER: + return state - 1 + default: + return state + } +} diff --git a/examples/universal-with-router/common/reducers/index.js b/examples/universal-with-router/common/reducers/index.js new file mode 100644 index 0000000000..6b03ad37b5 --- /dev/null +++ b/examples/universal-with-router/common/reducers/index.js @@ -0,0 +1,10 @@ +import { combineReducers } from 'redux' +import counter from './counter' +import { routeReducer } from 'react-router-redux' + +const rootReducer = combineReducers({ + routing: routeReducer, + counter +}) + +export default rootReducer diff --git a/examples/universal-with-router/common/routes.js b/examples/universal-with-router/common/routes.js new file mode 100644 index 0000000000..8d3c89c283 --- /dev/null +++ b/examples/universal-with-router/common/routes.js @@ -0,0 +1,15 @@ +import React from 'react' +import {Route, IndexRoute} from 'react-router' + +import Layout from './components/Layout' +import Home from './components/Home' +import Sample from './components/Sample' +import Counter from './containers/Counter' + +export default ( + + + + + + ) diff --git a/examples/universal-with-router/common/store/configureStore.js b/examples/universal-with-router/common/store/configureStore.js new file mode 100644 index 0000000000..2443ebe5dd --- /dev/null +++ b/examples/universal-with-router/common/store/configureStore.js @@ -0,0 +1,24 @@ +import { createStore, applyMiddleware } from 'redux' +import thunk from 'redux-thunk' +import rootReducer from '../reducers' + +export default function configureStore(initialState, middlewares = []) { + + middlewares = [thunk, ...middlewares] + + const store = createStore( + rootReducer, + initialState, + applyMiddleware(...middlewares) + ) + + 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/universal-with-router/index.js b/examples/universal-with-router/index.js new file mode 100644 index 0000000000..b2cf35e14c --- /dev/null +++ b/examples/universal-with-router/index.js @@ -0,0 +1 @@ +require('./client') diff --git a/examples/universal-with-router/package.json b/examples/universal-with-router/package.json new file mode 100644 index 0000000000..1392808413 --- /dev/null +++ b/examples/universal-with-router/package.json @@ -0,0 +1,42 @@ +{ + "name": "redux-universal-example", + "version": "0.0.0", + "description": "An example of a universally-rendered Redux application", + "scripts": { + "start": "node server/index.js" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/reactjs/redux.git" + }, + "license": "MIT", + "bugs": { + "url": "https://github.com/reactjs/redux/issues" + }, + "homepage": "http://redux.js.org", + "dependencies": { + "babel-polyfill": "^6.3.14", + "babel-register": "^6.4.3", + "express": "^4.13.3", + "qs": "^4.0.0", + "react": "^0.14.7", + "react-dom": "^0.14.7", + "react-redux": "^4.2.1", + "react-router": "^2.0.0", + "react-router-redux": "^3.0.0", + "redux": "^3.2.1", + "redux-thunk": "^1.0.3", + "serve-static": "^1.10.0" + }, + "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", + "babel-runtime": "^6.3.13", + "webpack": "^1.11.0", + "webpack-dev-middleware": "^1.4.0", + "webpack-hot-middleware": "^2.6.0" + } +} diff --git a/examples/universal-with-router/server/index.js b/examples/universal-with-router/server/index.js new file mode 100644 index 0000000000..04171d26bf --- /dev/null +++ b/examples/universal-with-router/server/index.js @@ -0,0 +1,2 @@ +require('babel-register') +require('./server') diff --git a/examples/universal-with-router/server/server.js b/examples/universal-with-router/server/server.js new file mode 100644 index 0000000000..b6607fd6b3 --- /dev/null +++ b/examples/universal-with-router/server/server.js @@ -0,0 +1,105 @@ +/* eslint-disable no-console, no-use-before-define */ + +import path from 'path' +import Express from 'express' + +import webpack from 'webpack' +import webpackDevMiddleware from 'webpack-dev-middleware' +import webpackHotMiddleware from 'webpack-hot-middleware' +import webpackConfig from '../webpack.config' + +import React from 'react' +import {renderToString} from 'react-dom/server' +import {Provider} from 'react-redux' +import {match, RouterContext} from 'react-router' + +import configureStore from '../common/store/configureStore' +import {fetchCounter} from '../common/api/counter' +import routes from '../common/routes' + +const app = new Express() +const port = 3000 + +// Use this middleware to set up hot module reloading via webpack. +const compiler = webpack(webpackConfig) +app.use(webpackDevMiddleware(compiler, { + noInfo: true, + publicPath: webpackConfig.output.publicPath +})) +app.use(webpackHotMiddleware(compiler)) + +// This is fired every time the server side receives a request +app.use(handleRender) + +function handleRender(req, res) { + + match({ + routes, + location: req.url + }, (error, redirectLocation, renderProps) => { + if (error) { + res.status(500).send(error.message) + } else if (redirectLocation) { + res.redirect(302, redirectLocation.pathname + redirectLocation.search) + } else if (renderProps) { + + // Query our mock API asynchronously + fetchCounter(apiResult => { + // Read the counter from the request, if provided + const counter = parseInt(req.params.counter, 10) || apiResult || 0 + + // Compile an initial state + const initialState = { + counter + } + + // Create a new Redux store instance + const store = configureStore(initialState) + + // You can also check renderProps.components or renderProps.routes for + // your "not found" component or route respectively, and send a 404 as + // below, if you're using a catch-all route. + + const html = renderToString( + + + + ) + + const finalState = store.getState() + + res.status(200).send(renderFullPage(html, finalState)) + }) + + } else { + res.status(404).send('Not found') + } + }) + +} + +function renderFullPage(html, initialState) { + return ` + + + + Redux Universal Example + + +
${html}
+ + + + + ` +} + +app.listen(port, (error) => { + if (error) { + console.error(error) + } else { + console.info(`==> 🌎 Listening on port ${port}. Open up http://localhost:${port}/ in your browser.`) + } +}) diff --git a/examples/universal-with-router/webpack.config.js b/examples/universal-with-router/webpack.config.js new file mode 100644 index 0000000000..3bea0f83af --- /dev/null +++ b/examples/universal-with-router/webpack.config.js @@ -0,0 +1,51 @@ +var path = require('path') +var webpack = require('webpack') + +module.exports = { + devtool: 'inline-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(), + new webpack.NoErrorsPlugin() + ], + module: { + loaders: [ + { + test: /\.js$/, + loader: 'babel', + exclude: /node_modules/, + include: __dirname, + query: { + presets: [ 'react-hmre' ] + } + } + ] + } +} + +// When inside Redux repo, prefer src to compiled version. +// You can safely delete these lines in your project. +var reduxSrc = path.join(__dirname, '..', '..', 'src') +var reduxNodeModules = path.join(__dirname, '..', '..', 'node_modules') +var fs = require('fs') +if (fs.existsSync(reduxSrc) && fs.existsSync(reduxNodeModules)) { + // Resolve Redux to source + module.exports.resolve = { alias: { 'redux': reduxSrc } } + // Our root .babelrc needs this flag for CommonJS output + process.env.BABEL_ENV = 'commonjs' + // Compile Redux from source + module.exports.module.loaders.push({ + test: /\.js$/, + loaders: [ 'babel' ], + include: reduxSrc, + }) +} From c8ab1261d5c986f3b391d3a105465d86de31668c Mon Sep 17 00:00:00 2001 From: xulien Date: Mon, 15 Feb 2016 13:53:27 +0100 Subject: [PATCH 02/44] cleaning --- examples/universal-with-router/client/index.js | 5 ++--- examples/universal-with-router/common/routes.js | 2 +- .../universal-with-router/common/store/configureStore.js | 2 +- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/examples/universal-with-router/client/index.js b/examples/universal-with-router/client/index.js index b6565eb326..6c29018076 100644 --- a/examples/universal-with-router/client/index.js +++ b/examples/universal-with-router/client/index.js @@ -2,16 +2,15 @@ import 'babel-polyfill' import React from 'react' import { render } from 'react-dom' import { Provider } from 'react-redux' -import { Router, Route, browserHistory } from 'react-router' +import { Router, browserHistory } from 'react-router' import { syncHistory } from 'react-router-redux' import configureStore from '../common/store/configureStore' -import reducers from '../common/reducers' import routes from '../common/routes' const initialState = window.__INITIAL_STATE__ const reduxRouterMiddleware = syncHistory(browserHistory) -const store = configureStore(initialState, [reduxRouterMiddleware]) +const store = configureStore(initialState, [ reduxRouterMiddleware ]) const rootElement = document.getElementById('app') reduxRouterMiddleware.listenForReplays(store) diff --git a/examples/universal-with-router/common/routes.js b/examples/universal-with-router/common/routes.js index 8d3c89c283..41a62a58b7 100644 --- a/examples/universal-with-router/common/routes.js +++ b/examples/universal-with-router/common/routes.js @@ -1,5 +1,5 @@ import React from 'react' -import {Route, IndexRoute} from 'react-router' +import { Route, IndexRoute } from 'react-router' import Layout from './components/Layout' import Home from './components/Home' diff --git a/examples/universal-with-router/common/store/configureStore.js b/examples/universal-with-router/common/store/configureStore.js index 2443ebe5dd..62e7c8e457 100644 --- a/examples/universal-with-router/common/store/configureStore.js +++ b/examples/universal-with-router/common/store/configureStore.js @@ -4,7 +4,7 @@ import rootReducer from '../reducers' export default function configureStore(initialState, middlewares = []) { - middlewares = [thunk, ...middlewares] + middlewares = [ thunk, ...middlewares ] const store = createStore( rootReducer, From bf0fafb051d8d8952a8e2008469235c0ae14b242 Mon Sep 17 00:00:00 2001 From: xulien Date: Mon, 15 Feb 2016 15:47:17 +0100 Subject: [PATCH 03/44] update to react-router-redux 4.x --- examples/universal-with-router/client/index.js | 11 +++++++---- .../universal-with-router/common/reducers/index.js | 4 ++-- .../common/store/configureStore.js | 6 ++---- examples/universal-with-router/package.json | 2 +- 4 files changed, 12 insertions(+), 11 deletions(-) diff --git a/examples/universal-with-router/client/index.js b/examples/universal-with-router/client/index.js index 6c29018076..624cd22e8e 100644 --- a/examples/universal-with-router/client/index.js +++ b/examples/universal-with-router/client/index.js @@ -2,18 +2,21 @@ import 'babel-polyfill' import React from 'react' import { render } from 'react-dom' import { Provider } from 'react-redux' +import thunk from 'redux-thunk' import { Router, browserHistory } from 'react-router' -import { syncHistory } from 'react-router-redux' +import { syncHistoryWithStore} from 'react-router-redux' import configureStore from '../common/store/configureStore' import routes from '../common/routes' +import reducers from '../common/reducers' + const initialState = window.__INITIAL_STATE__ -const reduxRouterMiddleware = syncHistory(browserHistory) -const store = configureStore(initialState, [ reduxRouterMiddleware ]) +const store = configureStore(initialState) const rootElement = document.getElementById('app') -reduxRouterMiddleware.listenForReplays(store) +// Create an enhanced history that syncs navigation events with the store +const history = syncHistoryWithStore(browserHistory, store) render( diff --git a/examples/universal-with-router/common/reducers/index.js b/examples/universal-with-router/common/reducers/index.js index 6b03ad37b5..10a51b50b2 100644 --- a/examples/universal-with-router/common/reducers/index.js +++ b/examples/universal-with-router/common/reducers/index.js @@ -1,9 +1,9 @@ import { combineReducers } from 'redux' import counter from './counter' -import { routeReducer } from 'react-router-redux' +import { routerReducer } from 'react-router-redux' const rootReducer = combineReducers({ - routing: routeReducer, + routing: routerReducer, counter }) diff --git a/examples/universal-with-router/common/store/configureStore.js b/examples/universal-with-router/common/store/configureStore.js index 62e7c8e457..b6886da86a 100644 --- a/examples/universal-with-router/common/store/configureStore.js +++ b/examples/universal-with-router/common/store/configureStore.js @@ -2,14 +2,12 @@ import { createStore, applyMiddleware } from 'redux' import thunk from 'redux-thunk' import rootReducer from '../reducers' -export default function configureStore(initialState, middlewares = []) { - - middlewares = [ thunk, ...middlewares ] +export default function configureStore(initialState) { const store = createStore( rootReducer, initialState, - applyMiddleware(...middlewares) + applyMiddleware(thunk) ) if (module.hot) { diff --git a/examples/universal-with-router/package.json b/examples/universal-with-router/package.json index 1392808413..50a1754a6f 100644 --- a/examples/universal-with-router/package.json +++ b/examples/universal-with-router/package.json @@ -23,7 +23,7 @@ "react-dom": "^0.14.7", "react-redux": "^4.2.1", "react-router": "^2.0.0", - "react-router-redux": "^3.0.0", + "react-router-redux": ">4.0.0-beta", "redux": "^3.2.1", "redux-thunk": "^1.0.3", "serve-static": "^1.10.0" From 46dd1201897bd7f31cd53ed1b6bfa48694b9e112 Mon Sep 17 00:00:00 2001 From: xulien Date: Mon, 15 Feb 2016 15:51:12 +0100 Subject: [PATCH 04/44] cleaning --- examples/universal-with-router/package.json | 1 - 1 file changed, 1 deletion(-) diff --git a/examples/universal-with-router/package.json b/examples/universal-with-router/package.json index 50a1754a6f..6d3445cae8 100644 --- a/examples/universal-with-router/package.json +++ b/examples/universal-with-router/package.json @@ -18,7 +18,6 @@ "babel-polyfill": "^6.3.14", "babel-register": "^6.4.3", "express": "^4.13.3", - "qs": "^4.0.0", "react": "^0.14.7", "react-dom": "^0.14.7", "react-redux": "^4.2.1", From 6426b3f0f16f08cc7a3e1b7735efaeb8d87ac8f6 Mon Sep 17 00:00:00 2001 From: xulien Date: Mon, 15 Feb 2016 15:58:32 +0100 Subject: [PATCH 05/44] cleaning --- examples/universal-with-router/client/index.js | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/examples/universal-with-router/client/index.js b/examples/universal-with-router/client/index.js index 624cd22e8e..09a4afa9e9 100644 --- a/examples/universal-with-router/client/index.js +++ b/examples/universal-with-router/client/index.js @@ -2,15 +2,12 @@ import 'babel-polyfill' import React from 'react' import { render } from 'react-dom' import { Provider } from 'react-redux' -import thunk from 'redux-thunk' import { Router, browserHistory } from 'react-router' -import { syncHistoryWithStore} from 'react-router-redux' +import { syncHistoryWithStore } from 'react-router-redux' import configureStore from '../common/store/configureStore' import routes from '../common/routes' -import reducers from '../common/reducers' - const initialState = window.__INITIAL_STATE__ const store = configureStore(initialState) const rootElement = document.getElementById('app') @@ -20,7 +17,7 @@ const history = syncHistoryWithStore(browserHistory, store) render( - + {routes} , From f7ec1a979ac3076fff5df767874cdc25d44f6e38 Mon Sep 17 00:00:00 2001 From: xulien Date: Wed, 17 Feb 2016 15:54:45 +0100 Subject: [PATCH 06/44] init --- examples/async-router/.babelrc | 9 ++ examples/async-router/actions/index.js | 64 +++++++++++ examples/async-router/components/Home.js | 13 +++ examples/async-router/components/Layout.js | 19 ++++ examples/async-router/components/Picker.js | 29 +++++ examples/async-router/components/Posts.js | 17 +++ examples/async-router/components/Sample.js | 11 ++ examples/async-router/components/Test.js | 13 +++ examples/async-router/containers/Reddit.js | 100 ++++++++++++++++++ examples/async-router/index.html | 11 ++ examples/async-router/index.js | 19 ++++ examples/async-router/package.json | 53 ++++++++++ examples/async-router/reducers/index.js | 61 +++++++++++ examples/async-router/routes.js | 19 ++++ examples/async-router/server.js | 23 ++++ examples/async-router/store/configureStore.js | 22 ++++ examples/async-router/webpack.config.js | 49 +++++++++ 17 files changed, 532 insertions(+) create mode 100644 examples/async-router/.babelrc create mode 100644 examples/async-router/actions/index.js create mode 100644 examples/async-router/components/Home.js create mode 100644 examples/async-router/components/Layout.js create mode 100644 examples/async-router/components/Picker.js create mode 100644 examples/async-router/components/Posts.js create mode 100644 examples/async-router/components/Sample.js create mode 100644 examples/async-router/components/Test.js create mode 100644 examples/async-router/containers/Reddit.js create mode 100644 examples/async-router/index.html create mode 100644 examples/async-router/index.js create mode 100644 examples/async-router/package.json create mode 100644 examples/async-router/reducers/index.js create mode 100644 examples/async-router/routes.js create mode 100644 examples/async-router/server.js create mode 100644 examples/async-router/store/configureStore.js create mode 100644 examples/async-router/webpack.config.js diff --git a/examples/async-router/.babelrc b/examples/async-router/.babelrc new file mode 100644 index 0000000000..b46ff35077 --- /dev/null +++ b/examples/async-router/.babelrc @@ -0,0 +1,9 @@ +{ + "presets": ["es2015", "react"], + "plugins": ["transform-class-properties"], // needed to get local context.router + "env": { + "development": { + "presets": ["react-hmre"] + } + } +} diff --git a/examples/async-router/actions/index.js b/examples/async-router/actions/index.js new file mode 100644 index 0000000000..b3c32cd68d --- /dev/null +++ b/examples/async-router/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-router/components/Home.js b/examples/async-router/components/Home.js new file mode 100644 index 0000000000..d6cf0a2082 --- /dev/null +++ b/examples/async-router/components/Home.js @@ -0,0 +1,13 @@ +import React, { Component } from 'react' +import Link from 'react-router/lib/Link' + +export default class Home extends Component { + render() { + return ( +
+

Home page

+

Frontend

+
+ ) + } +} diff --git a/examples/async-router/components/Layout.js b/examples/async-router/components/Layout.js new file mode 100644 index 0000000000..b39e09d2fa --- /dev/null +++ b/examples/async-router/components/Layout.js @@ -0,0 +1,19 @@ +import React, { Component } from 'react' +import Link from 'react-router/lib/Link' + +export default class Layout extends Component { + render() { + return ( +
+ + {this.props.children} +
+ ) + } +} diff --git a/examples/async-router/components/Picker.js b/examples/async-router/components/Picker.js new file mode 100644 index 0000000000..be78acb182 --- /dev/null +++ b/examples/async-router/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}

+ +
+ ) + } +} + +Picker.propTypes = { + options: PropTypes.arrayOf( + PropTypes.string.isRequired + ).isRequired, + value: PropTypes.string.isRequired, + onChange: PropTypes.func.isRequired +} diff --git a/examples/async-router/components/Posts.js b/examples/async-router/components/Posts.js new file mode 100644 index 0000000000..dd3285dab9 --- /dev/null +++ b/examples/async-router/components/Posts.js @@ -0,0 +1,17 @@ +import React, { PropTypes, Component } from 'react' + +export default class Posts extends Component { + render() { + return ( +
    + {this.props.posts.map((post, i) => +
  • {post.title}
  • + )} +
+ ) + } +} + +Posts.propTypes = { + posts: PropTypes.array.isRequired +} diff --git a/examples/async-router/components/Sample.js b/examples/async-router/components/Sample.js new file mode 100644 index 0000000000..2a88c3c77e --- /dev/null +++ b/examples/async-router/components/Sample.js @@ -0,0 +1,11 @@ +import React, { Component } from 'react' + +export default class Sample extends Component { + render() { + return ( +
+

Sample page

+
+ ) + } +} diff --git a/examples/async-router/components/Test.js b/examples/async-router/components/Test.js new file mode 100644 index 0000000000..0bed45f05a --- /dev/null +++ b/examples/async-router/components/Test.js @@ -0,0 +1,13 @@ +import React, { Component } from 'react' + +export default class Test extends Component { + + render() { + console.log('test fired'); + return ( +
+

test page

+
+ ) + } +} diff --git a/examples/async-router/containers/Reddit.js b/examples/async-router/containers/Reddit.js new file mode 100644 index 0000000000..f5a686d8e0 --- /dev/null +++ b/examples/async-router/containers/Reddit.js @@ -0,0 +1,100 @@ +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 { + + static contextTypes = { + router: PropTypes.object + } + + constructor(props) { + super(props) + this.handleChange = this.handleChange.bind(this) + this.handleRefreshClick = this.handleRefreshClick.bind(this) + this.rootPath = location.pathname.substring(0, location.pathname.lastIndexOf("/") + 1) + } + + componentDidMount() { + const { dispatch, selectedReddit, params } = this.props + dispatch(selectReddit(params.id)) + dispatch(fetchPostsIfNeeded(selectedReddit)) + } + + componentWillReceiveProps(nextProps) { + const { dispatch, params } = this.props + if ( nextProps.params.id !== params.id ) { + dispatch(selectReddit(nextProps.params.id)) + dispatch(fetchPostsIfNeeded(nextProps.params.id)) + } + } + + handleChange(nextReddit) { + this.context.router.push(this.rootPath + 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 && + Refresh + +} +

+ {isEmpty + ? (isFetching + ?

Loading...

+ :

Empty.

) + :
+ +
+} +
+ ) + } +} + +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: true, + items: [] + } + + return {selectedReddit, posts, isFetching, lastUpdated} +} + +export default connect(mapStateToProps)(Reddit) diff --git a/examples/async-router/index.html b/examples/async-router/index.html new file mode 100644 index 0000000000..b89496eae0 --- /dev/null +++ b/examples/async-router/index.html @@ -0,0 +1,11 @@ + + + + Redux async example + + +
+
+ + + diff --git a/examples/async-router/index.js b/examples/async-router/index.js new file mode 100644 index 0000000000..2c8735b9b7 --- /dev/null +++ b/examples/async-router/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-router/package.json b/examples/async-router/package.json new file mode 100644 index 0000000000..c82f3e19a2 --- /dev/null +++ b/examples/async-router/package.json @@ -0,0 +1,53 @@ +{ + "name": "redux-async-example", + "version": "0.0.0", + "description": "Redux async 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-plugin-transform-class-properties": "^6.5.2", + "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-router/reducers/index.js b/examples/async-router/reducers/index.js new file mode 100644 index 0000000000..f936cbced3 --- /dev/null +++ b/examples/async-router/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 = '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 diff --git a/examples/async-router/routes.js b/examples/async-router/routes.js new file mode 100644 index 0000000000..9af960d977 --- /dev/null +++ b/examples/async-router/routes.js @@ -0,0 +1,19 @@ +import React from 'react' +import Route from 'react-router/lib/Route' +import IndexRoute from 'react-router/lib/IndexRoute' +import Redirect from 'react-router/lib/Redirect' + +import Layout from './components/Layout' +import Home from './components/Home' +import Sample from './components/Sample' +import Reddit from './containers/Reddit' +import Test from './components/Test' + +export default ( + + + + + + + ) diff --git a/examples/async-router/server.js b/examples/async-router/server.js new file mode 100644 index 0000000000..9aeb674633 --- /dev/null +++ b/examples/async-router/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-router/store/configureStore.js b/examples/async-router/store/configureStore.js new file mode 100644 index 0000000000..465d94919a --- /dev/null +++ b/examples/async-router/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-router/webpack.config.js b/examples/async-router/webpack.config.js new file mode 100644 index 0000000000..f71daecee2 --- /dev/null +++ b/examples/async-router/webpack.config.js @@ -0,0 +1,49 @@ +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(), + new webpack.NoErrorsPlugin() + ], + module: { + loaders: [ + { + test: /\.js$/, + loaders: ['babel'], + exclude: /node_modules/, + include: __dirname + } + ] + } +} + + +// When inside Redux repo, prefer src to compiled version. +// You can safely delete these lines in your project. +var reduxSrc = path.join(__dirname, '..', '..', 'src') +var reduxNodeModules = path.join(__dirname, '..', '..', 'node_modules') +var fs = require('fs') +if (fs.existsSync(reduxSrc) && fs.existsSync(reduxNodeModules)) { + // Resolve Redux to source + module.exports.resolve = { alias: { 'redux': reduxSrc } } + // Our root .babelrc needs this flag for CommonJS output + process.env.BABEL_ENV = 'commonjs' + // Compile Redux from source + module.exports.module.loaders.push({ + test: /\.js$/, + loaders: ['babel'], + include: reduxSrc + }) +} From e50bcad674b54466b8bed3379afd6c9079b48ad7 Mon Sep 17 00:00:00 2001 From: xulien Date: Wed, 17 Feb 2016 16:05:42 +0100 Subject: [PATCH 07/44] async-router example --- examples/async-router/components/Test.js | 13 ------------- examples/async-router/containers/Reddit.js | 10 +++++----- examples/async-router/routes.js | 1 - 3 files changed, 5 insertions(+), 19 deletions(-) delete mode 100644 examples/async-router/components/Test.js diff --git a/examples/async-router/components/Test.js b/examples/async-router/components/Test.js deleted file mode 100644 index 0bed45f05a..0000000000 --- a/examples/async-router/components/Test.js +++ /dev/null @@ -1,13 +0,0 @@ -import React, { Component } from 'react' - -export default class Test extends Component { - - render() { - console.log('test fired'); - return ( -
-

test page

-
- ) - } -} diff --git a/examples/async-router/containers/Reddit.js b/examples/async-router/containers/Reddit.js index f5a686d8e0..688a0dbcc2 100644 --- a/examples/async-router/containers/Reddit.js +++ b/examples/async-router/containers/Reddit.js @@ -1,6 +1,6 @@ -import React, {Component, PropTypes} from 'react' -import {connect} from 'react-redux' -import {selectReddit, fetchPostsIfNeeded, invalidateReddit} from '../actions' +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' @@ -14,7 +14,7 @@ class Reddit extends Component { super(props) this.handleChange = this.handleChange.bind(this) this.handleRefreshClick = this.handleRefreshClick.bind(this) - this.rootPath = location.pathname.substring(0, location.pathname.lastIndexOf("/") + 1) + this.rootPath = location.pathname.substring(0, location.pathname.lastIndexOf('/') + 1) } componentDidMount() { @@ -49,7 +49,7 @@ class Reddit extends Component { return (
- +

{lastUpdated && Last updated at diff --git a/examples/async-router/routes.js b/examples/async-router/routes.js index 9af960d977..bf5287ffb5 100644 --- a/examples/async-router/routes.js +++ b/examples/async-router/routes.js @@ -7,7 +7,6 @@ import Layout from './components/Layout' import Home from './components/Home' import Sample from './components/Sample' import Reddit from './containers/Reddit' -import Test from './components/Test' export default ( From 0fd5f12c19006aa2d23f193165743524109f075d Mon Sep 17 00:00:00 2001 From: xulien Date: Wed, 17 Feb 2016 16:10:01 +0100 Subject: [PATCH 08/44] async-with-routing example --- examples/{async-router => async-with-routing}/.babelrc | 0 examples/{async-router => async-with-routing}/actions/index.js | 0 examples/{async-router => async-with-routing}/components/Home.js | 0 .../{async-router => async-with-routing}/components/Layout.js | 0 .../{async-router => async-with-routing}/components/Picker.js | 0 examples/{async-router => async-with-routing}/components/Posts.js | 0 .../{async-router => async-with-routing}/components/Sample.js | 0 .../{async-router => async-with-routing}/containers/Reddit.js | 0 examples/{async-router => async-with-routing}/index.html | 0 examples/{async-router => async-with-routing}/index.js | 0 examples/{async-router => async-with-routing}/package.json | 0 examples/{async-router => async-with-routing}/reducers/index.js | 0 examples/{async-router => async-with-routing}/routes.js | 0 examples/{async-router => async-with-routing}/server.js | 0 .../{async-router => async-with-routing}/store/configureStore.js | 0 examples/{async-router => async-with-routing}/webpack.config.js | 0 16 files changed, 0 insertions(+), 0 deletions(-) rename examples/{async-router => async-with-routing}/.babelrc (100%) rename examples/{async-router => async-with-routing}/actions/index.js (100%) rename examples/{async-router => async-with-routing}/components/Home.js (100%) rename examples/{async-router => async-with-routing}/components/Layout.js (100%) rename examples/{async-router => async-with-routing}/components/Picker.js (100%) rename examples/{async-router => async-with-routing}/components/Posts.js (100%) rename examples/{async-router => async-with-routing}/components/Sample.js (100%) rename examples/{async-router => async-with-routing}/containers/Reddit.js (100%) rename examples/{async-router => async-with-routing}/index.html (100%) rename examples/{async-router => async-with-routing}/index.js (100%) rename examples/{async-router => async-with-routing}/package.json (100%) rename examples/{async-router => async-with-routing}/reducers/index.js (100%) rename examples/{async-router => async-with-routing}/routes.js (100%) rename examples/{async-router => async-with-routing}/server.js (100%) rename examples/{async-router => async-with-routing}/store/configureStore.js (100%) rename examples/{async-router => async-with-routing}/webpack.config.js (100%) diff --git a/examples/async-router/.babelrc b/examples/async-with-routing/.babelrc similarity index 100% rename from examples/async-router/.babelrc rename to examples/async-with-routing/.babelrc diff --git a/examples/async-router/actions/index.js b/examples/async-with-routing/actions/index.js similarity index 100% rename from examples/async-router/actions/index.js rename to examples/async-with-routing/actions/index.js diff --git a/examples/async-router/components/Home.js b/examples/async-with-routing/components/Home.js similarity index 100% rename from examples/async-router/components/Home.js rename to examples/async-with-routing/components/Home.js diff --git a/examples/async-router/components/Layout.js b/examples/async-with-routing/components/Layout.js similarity index 100% rename from examples/async-router/components/Layout.js rename to examples/async-with-routing/components/Layout.js diff --git a/examples/async-router/components/Picker.js b/examples/async-with-routing/components/Picker.js similarity index 100% rename from examples/async-router/components/Picker.js rename to examples/async-with-routing/components/Picker.js diff --git a/examples/async-router/components/Posts.js b/examples/async-with-routing/components/Posts.js similarity index 100% rename from examples/async-router/components/Posts.js rename to examples/async-with-routing/components/Posts.js diff --git a/examples/async-router/components/Sample.js b/examples/async-with-routing/components/Sample.js similarity index 100% rename from examples/async-router/components/Sample.js rename to examples/async-with-routing/components/Sample.js diff --git a/examples/async-router/containers/Reddit.js b/examples/async-with-routing/containers/Reddit.js similarity index 100% rename from examples/async-router/containers/Reddit.js rename to examples/async-with-routing/containers/Reddit.js diff --git a/examples/async-router/index.html b/examples/async-with-routing/index.html similarity index 100% rename from examples/async-router/index.html rename to examples/async-with-routing/index.html diff --git a/examples/async-router/index.js b/examples/async-with-routing/index.js similarity index 100% rename from examples/async-router/index.js rename to examples/async-with-routing/index.js diff --git a/examples/async-router/package.json b/examples/async-with-routing/package.json similarity index 100% rename from examples/async-router/package.json rename to examples/async-with-routing/package.json diff --git a/examples/async-router/reducers/index.js b/examples/async-with-routing/reducers/index.js similarity index 100% rename from examples/async-router/reducers/index.js rename to examples/async-with-routing/reducers/index.js diff --git a/examples/async-router/routes.js b/examples/async-with-routing/routes.js similarity index 100% rename from examples/async-router/routes.js rename to examples/async-with-routing/routes.js diff --git a/examples/async-router/server.js b/examples/async-with-routing/server.js similarity index 100% rename from examples/async-router/server.js rename to examples/async-with-routing/server.js diff --git a/examples/async-router/store/configureStore.js b/examples/async-with-routing/store/configureStore.js similarity index 100% rename from examples/async-router/store/configureStore.js rename to examples/async-with-routing/store/configureStore.js diff --git a/examples/async-router/webpack.config.js b/examples/async-with-routing/webpack.config.js similarity index 100% rename from examples/async-router/webpack.config.js rename to examples/async-with-routing/webpack.config.js From 7393f908cc8e33f263d2d742a3085f6bfe4f56dc Mon Sep 17 00:00:00 2001 From: xulien Date: Wed, 17 Feb 2016 16:20:26 +0100 Subject: [PATCH 09/44] cleaning --- examples/async-router/containers/Reddit.js | 100 +++++++++++++++++++++ 1 file changed, 100 insertions(+) create mode 100644 examples/async-router/containers/Reddit.js diff --git a/examples/async-router/containers/Reddit.js b/examples/async-router/containers/Reddit.js new file mode 100644 index 0000000000..4ea254d627 --- /dev/null +++ b/examples/async-router/containers/Reddit.js @@ -0,0 +1,100 @@ +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 { + + static contextTypes = { + router: PropTypes.object + } + + constructor(props) { + super(props) + this.handleChange = this.handleChange.bind(this) + this.handleRefreshClick = this.handleRefreshClick.bind(this) + this.rootPath = location.pathname.substring(0, location.pathname.lastIndexOf('/') + 1) + } + + componentDidMount() { + const { dispatch, selectedReddit, params } = this.props + dispatch(selectReddit(params.id)) + dispatch(fetchPostsIfNeeded(selectedReddit)) + } + + componentWillReceiveProps(nextProps) { + const { dispatch, params } = this.props + if ( nextProps.params.id !== params.id ) { + dispatch(selectReddit(nextProps.params.id)) + dispatch(fetchPostsIfNeeded(nextProps.params.id)) + } + } + + handleChange(nextReddit) { + this.context.router.push(this.rootPath + 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 && + Refresh + +} +

+ {isEmpty + ? (isFetching + ?

Loading...

+ :

Empty.

) + :
+ +
+} +
+ ) + } +} + +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: true, + items: [] + } + + return { selectedReddit, posts, isFetching, lastUpdated } +} + +export default connect(mapStateToProps)(Reddit) From 81b80068bef69497be17014711653ee559a5a129 Mon Sep 17 00:00:00 2001 From: xulien Date: Wed, 17 Feb 2016 16:49:18 +0100 Subject: [PATCH 10/44] cleaning --- examples/async-router/containers/Reddit.js | 100 ------------------ .../async-with-routing/containers/Reddit.js | 2 +- 2 files changed, 1 insertion(+), 101 deletions(-) delete mode 100644 examples/async-router/containers/Reddit.js diff --git a/examples/async-router/containers/Reddit.js b/examples/async-router/containers/Reddit.js deleted file mode 100644 index 4ea254d627..0000000000 --- a/examples/async-router/containers/Reddit.js +++ /dev/null @@ -1,100 +0,0 @@ -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 { - - static contextTypes = { - router: PropTypes.object - } - - constructor(props) { - super(props) - this.handleChange = this.handleChange.bind(this) - this.handleRefreshClick = this.handleRefreshClick.bind(this) - this.rootPath = location.pathname.substring(0, location.pathname.lastIndexOf('/') + 1) - } - - componentDidMount() { - const { dispatch, selectedReddit, params } = this.props - dispatch(selectReddit(params.id)) - dispatch(fetchPostsIfNeeded(selectedReddit)) - } - - componentWillReceiveProps(nextProps) { - const { dispatch, params } = this.props - if ( nextProps.params.id !== params.id ) { - dispatch(selectReddit(nextProps.params.id)) - dispatch(fetchPostsIfNeeded(nextProps.params.id)) - } - } - - handleChange(nextReddit) { - this.context.router.push(this.rootPath + 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 && - Refresh - -} -

- {isEmpty - ? (isFetching - ?

Loading...

- :

Empty.

) - :
- -
-} -
- ) - } -} - -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: true, - items: [] - } - - return { selectedReddit, posts, isFetching, lastUpdated } -} - -export default connect(mapStateToProps)(Reddit) diff --git a/examples/async-with-routing/containers/Reddit.js b/examples/async-with-routing/containers/Reddit.js index 688a0dbcc2..4ea254d627 100644 --- a/examples/async-with-routing/containers/Reddit.js +++ b/examples/async-with-routing/containers/Reddit.js @@ -94,7 +94,7 @@ function mapStateToProps(state) { items: [] } - return {selectedReddit, posts, isFetching, lastUpdated} + return { selectedReddit, posts, isFetching, lastUpdated } } export default connect(mapStateToProps)(Reddit) From 40f6ec66746c32507f20630625652d29ff7ea11e Mon Sep 17 00:00:00 2001 From: xulien Date: Wed, 17 Feb 2016 22:17:30 +0100 Subject: [PATCH 11/44] delete universal-with-router based on counter example --- examples/universal-with-router/.babelrc | 3 - .../universal-with-router/client/index.js | 25 ----- .../common/actions/index.js | 42 ------- .../common/api/counter.js | 13 --- .../common/components/Counter.js | 30 ----- .../common/components/Home.js | 11 -- .../common/components/Layout.js | 19 ---- .../common/components/Sample.js | 11 -- .../common/containers/Counter.js | 16 --- .../common/reducers/counter.js | 14 --- .../common/reducers/index.js | 10 -- .../universal-with-router/common/routes.js | 15 --- .../common/store/configureStore.js | 22 ---- examples/universal-with-router/index.js | 1 - examples/universal-with-router/package.json | 41 ------- .../universal-with-router/server/index.js | 2 - .../universal-with-router/server/server.js | 105 ------------------ .../universal-with-router/webpack.config.js | 51 --------- 18 files changed, 431 deletions(-) delete mode 100644 examples/universal-with-router/.babelrc delete mode 100644 examples/universal-with-router/client/index.js delete mode 100644 examples/universal-with-router/common/actions/index.js delete mode 100644 examples/universal-with-router/common/api/counter.js delete mode 100644 examples/universal-with-router/common/components/Counter.js delete mode 100644 examples/universal-with-router/common/components/Home.js delete mode 100644 examples/universal-with-router/common/components/Layout.js delete mode 100644 examples/universal-with-router/common/components/Sample.js delete mode 100644 examples/universal-with-router/common/containers/Counter.js delete mode 100644 examples/universal-with-router/common/reducers/counter.js delete mode 100644 examples/universal-with-router/common/reducers/index.js delete mode 100644 examples/universal-with-router/common/routes.js delete mode 100644 examples/universal-with-router/common/store/configureStore.js delete mode 100644 examples/universal-with-router/index.js delete mode 100644 examples/universal-with-router/package.json delete mode 100644 examples/universal-with-router/server/index.js delete mode 100644 examples/universal-with-router/server/server.js delete mode 100644 examples/universal-with-router/webpack.config.js diff --git a/examples/universal-with-router/.babelrc b/examples/universal-with-router/.babelrc deleted file mode 100644 index dd87fb5136..0000000000 --- a/examples/universal-with-router/.babelrc +++ /dev/null @@ -1,3 +0,0 @@ -{ - presets: ["es2015", "react"] -} \ No newline at end of file diff --git a/examples/universal-with-router/client/index.js b/examples/universal-with-router/client/index.js deleted file mode 100644 index 09a4afa9e9..0000000000 --- a/examples/universal-with-router/client/index.js +++ /dev/null @@ -1,25 +0,0 @@ -import 'babel-polyfill' -import React from 'react' -import { render } from 'react-dom' -import { Provider } from 'react-redux' -import { Router, browserHistory } from 'react-router' -import { syncHistoryWithStore } from 'react-router-redux' - -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') - -// Create an enhanced history that syncs navigation events with the store -const history = syncHistoryWithStore(browserHistory, store) - -render( - - - {routes} - - , - rootElement -) diff --git a/examples/universal-with-router/common/actions/index.js b/examples/universal-with-router/common/actions/index.js deleted file mode 100644 index cdd84fe881..0000000000 --- a/examples/universal-with-router/common/actions/index.js +++ /dev/null @@ -1,42 +0,0 @@ -export const SET_COUNTER = 'SET_COUNTER' -export const INCREMENT_COUNTER = 'INCREMENT_COUNTER' -export const DECREMENT_COUNTER = 'DECREMENT_COUNTER' - -export function set(value) { - return { - type: SET_COUNTER, - payload: value - } -} - -export function increment() { - return { - type: INCREMENT_COUNTER - } -} - -export function decrement() { - return { - type: DECREMENT_COUNTER - } -} - -export function incrementIfOdd() { - return (dispatch, getState) => { - const { counter } = getState() - - if (counter % 2 === 0) { - return - } - - dispatch(increment()) - } -} - -export function incrementAsync(delay = 1000) { - return dispatch => { - setTimeout(() => { - dispatch(increment()) - }, delay) - } -} diff --git a/examples/universal-with-router/common/api/counter.js b/examples/universal-with-router/common/api/counter.js deleted file mode 100644 index 2bdd1f3b64..0000000000 --- a/examples/universal-with-router/common/api/counter.js +++ /dev/null @@ -1,13 +0,0 @@ -function getRandomInt(min, max) { - return Math.floor(Math.random() * (max - min)) + min -} - -export function fetchCounter(callback) { - // Rather than immediately returning, we delay our code with a timeout to simulate asynchronous behavior - setTimeout(() => { - callback(getRandomInt(1, 100)) - }, 500) - - // In the case of a real world API call, you'll normally run into a Promise like this: - // API.getUser().then(user => callback(user)) -} diff --git a/examples/universal-with-router/common/components/Counter.js b/examples/universal-with-router/common/components/Counter.js deleted file mode 100644 index 61532854e4..0000000000 --- a/examples/universal-with-router/common/components/Counter.js +++ /dev/null @@ -1,30 +0,0 @@ -import React, { Component, PropTypes } from 'react' - -class Counter extends Component { - render() { - const { increment, incrementIfOdd, incrementAsync, decrement, counter } = this.props - return ( -

- Clicked: {counter} times - {' '} - - {' '} - - {' '} - - {' '} - -

- ) - } -} - -Counter.propTypes = { - increment: PropTypes.func.isRequired, - incrementIfOdd: PropTypes.func.isRequired, - incrementAsync: PropTypes.func.isRequired, - decrement: PropTypes.func.isRequired, - counter: PropTypes.number.isRequired -} - -export default Counter diff --git a/examples/universal-with-router/common/components/Home.js b/examples/universal-with-router/common/components/Home.js deleted file mode 100644 index 695b97dd18..0000000000 --- a/examples/universal-with-router/common/components/Home.js +++ /dev/null @@ -1,11 +0,0 @@ -import React, { Component } from 'react' - -export default class Home extends Component { - render() { - return ( -
-

Home page

-
- ) - } -} diff --git a/examples/universal-with-router/common/components/Layout.js b/examples/universal-with-router/common/components/Layout.js deleted file mode 100644 index 529bc67952..0000000000 --- a/examples/universal-with-router/common/components/Layout.js +++ /dev/null @@ -1,19 +0,0 @@ -import React, { Component } from 'react' -import { Link } from 'react-router' - -export default class Layout extends Component { - render() { - return ( -
- - {this.props.children} -
- ) - } -} diff --git a/examples/universal-with-router/common/components/Sample.js b/examples/universal-with-router/common/components/Sample.js deleted file mode 100644 index 4d50f444bb..0000000000 --- a/examples/universal-with-router/common/components/Sample.js +++ /dev/null @@ -1,11 +0,0 @@ -import React, { Component } from 'react' - -export default class Sample extends Component { - render() { - return ( -
-

Sample

-
- ) - } -} diff --git a/examples/universal-with-router/common/containers/Counter.js b/examples/universal-with-router/common/containers/Counter.js deleted file mode 100644 index 3a931cece5..0000000000 --- a/examples/universal-with-router/common/containers/Counter.js +++ /dev/null @@ -1,16 +0,0 @@ -import { bindActionCreators } from 'redux' -import { connect } from 'react-redux' -import Counter from '../components/Counter' -import * as CounterActions from '../actions' - -function mapStateToProps(state) { - return { - counter: state.counter - } -} - -function mapDispatchToProps(dispatch) { - return bindActionCreators(CounterActions, dispatch) -} - -export default connect(mapStateToProps, mapDispatchToProps)(Counter) diff --git a/examples/universal-with-router/common/reducers/counter.js b/examples/universal-with-router/common/reducers/counter.js deleted file mode 100644 index ba4458db85..0000000000 --- a/examples/universal-with-router/common/reducers/counter.js +++ /dev/null @@ -1,14 +0,0 @@ -import { SET_COUNTER, INCREMENT_COUNTER, DECREMENT_COUNTER } from '../actions' - -export default function counter(state = 0, action) { - switch (action.type) { - case SET_COUNTER: - return action.payload - case INCREMENT_COUNTER: - return state + 1 - case DECREMENT_COUNTER: - return state - 1 - default: - return state - } -} diff --git a/examples/universal-with-router/common/reducers/index.js b/examples/universal-with-router/common/reducers/index.js deleted file mode 100644 index 10a51b50b2..0000000000 --- a/examples/universal-with-router/common/reducers/index.js +++ /dev/null @@ -1,10 +0,0 @@ -import { combineReducers } from 'redux' -import counter from './counter' -import { routerReducer } from 'react-router-redux' - -const rootReducer = combineReducers({ - routing: routerReducer, - counter -}) - -export default rootReducer diff --git a/examples/universal-with-router/common/routes.js b/examples/universal-with-router/common/routes.js deleted file mode 100644 index 41a62a58b7..0000000000 --- a/examples/universal-with-router/common/routes.js +++ /dev/null @@ -1,15 +0,0 @@ -import React from 'react' -import { Route, IndexRoute } from 'react-router' - -import Layout from './components/Layout' -import Home from './components/Home' -import Sample from './components/Sample' -import Counter from './containers/Counter' - -export default ( - - - - - - ) diff --git a/examples/universal-with-router/common/store/configureStore.js b/examples/universal-with-router/common/store/configureStore.js deleted file mode 100644 index b6886da86a..0000000000 --- a/examples/universal-with-router/common/store/configureStore.js +++ /dev/null @@ -1,22 +0,0 @@ -import { createStore, applyMiddleware } from 'redux' -import thunk from 'redux-thunk' -import rootReducer from '../reducers' - -export default function configureStore(initialState) { - - const store = createStore( - rootReducer, - initialState, - applyMiddleware(thunk) - ) - - 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/universal-with-router/index.js b/examples/universal-with-router/index.js deleted file mode 100644 index b2cf35e14c..0000000000 --- a/examples/universal-with-router/index.js +++ /dev/null @@ -1 +0,0 @@ -require('./client') diff --git a/examples/universal-with-router/package.json b/examples/universal-with-router/package.json deleted file mode 100644 index 6d3445cae8..0000000000 --- a/examples/universal-with-router/package.json +++ /dev/null @@ -1,41 +0,0 @@ -{ - "name": "redux-universal-example", - "version": "0.0.0", - "description": "An example of a universally-rendered Redux application", - "scripts": { - "start": "node server/index.js" - }, - "repository": { - "type": "git", - "url": "git+https://github.com/reactjs/redux.git" - }, - "license": "MIT", - "bugs": { - "url": "https://github.com/reactjs/redux/issues" - }, - "homepage": "http://redux.js.org", - "dependencies": { - "babel-polyfill": "^6.3.14", - "babel-register": "^6.4.3", - "express": "^4.13.3", - "react": "^0.14.7", - "react-dom": "^0.14.7", - "react-redux": "^4.2.1", - "react-router": "^2.0.0", - "react-router-redux": ">4.0.0-beta", - "redux": "^3.2.1", - "redux-thunk": "^1.0.3", - "serve-static": "^1.10.0" - }, - "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", - "babel-runtime": "^6.3.13", - "webpack": "^1.11.0", - "webpack-dev-middleware": "^1.4.0", - "webpack-hot-middleware": "^2.6.0" - } -} diff --git a/examples/universal-with-router/server/index.js b/examples/universal-with-router/server/index.js deleted file mode 100644 index 04171d26bf..0000000000 --- a/examples/universal-with-router/server/index.js +++ /dev/null @@ -1,2 +0,0 @@ -require('babel-register') -require('./server') diff --git a/examples/universal-with-router/server/server.js b/examples/universal-with-router/server/server.js deleted file mode 100644 index b6607fd6b3..0000000000 --- a/examples/universal-with-router/server/server.js +++ /dev/null @@ -1,105 +0,0 @@ -/* eslint-disable no-console, no-use-before-define */ - -import path from 'path' -import Express from 'express' - -import webpack from 'webpack' -import webpackDevMiddleware from 'webpack-dev-middleware' -import webpackHotMiddleware from 'webpack-hot-middleware' -import webpackConfig from '../webpack.config' - -import React from 'react' -import {renderToString} from 'react-dom/server' -import {Provider} from 'react-redux' -import {match, RouterContext} from 'react-router' - -import configureStore from '../common/store/configureStore' -import {fetchCounter} from '../common/api/counter' -import routes from '../common/routes' - -const app = new Express() -const port = 3000 - -// Use this middleware to set up hot module reloading via webpack. -const compiler = webpack(webpackConfig) -app.use(webpackDevMiddleware(compiler, { - noInfo: true, - publicPath: webpackConfig.output.publicPath -})) -app.use(webpackHotMiddleware(compiler)) - -// This is fired every time the server side receives a request -app.use(handleRender) - -function handleRender(req, res) { - - match({ - routes, - location: req.url - }, (error, redirectLocation, renderProps) => { - if (error) { - res.status(500).send(error.message) - } else if (redirectLocation) { - res.redirect(302, redirectLocation.pathname + redirectLocation.search) - } else if (renderProps) { - - // Query our mock API asynchronously - fetchCounter(apiResult => { - // Read the counter from the request, if provided - const counter = parseInt(req.params.counter, 10) || apiResult || 0 - - // Compile an initial state - const initialState = { - counter - } - - // Create a new Redux store instance - const store = configureStore(initialState) - - // You can also check renderProps.components or renderProps.routes for - // your "not found" component or route respectively, and send a 404 as - // below, if you're using a catch-all route. - - const html = renderToString( - - - - ) - - const finalState = store.getState() - - res.status(200).send(renderFullPage(html, finalState)) - }) - - } else { - res.status(404).send('Not found') - } - }) - -} - -function renderFullPage(html, initialState) { - return ` - - - - Redux Universal Example - - -
${html}
- - - - - ` -} - -app.listen(port, (error) => { - if (error) { - console.error(error) - } else { - console.info(`==> 🌎 Listening on port ${port}. Open up http://localhost:${port}/ in your browser.`) - } -}) diff --git a/examples/universal-with-router/webpack.config.js b/examples/universal-with-router/webpack.config.js deleted file mode 100644 index 3bea0f83af..0000000000 --- a/examples/universal-with-router/webpack.config.js +++ /dev/null @@ -1,51 +0,0 @@ -var path = require('path') -var webpack = require('webpack') - -module.exports = { - devtool: 'inline-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(), - new webpack.NoErrorsPlugin() - ], - module: { - loaders: [ - { - test: /\.js$/, - loader: 'babel', - exclude: /node_modules/, - include: __dirname, - query: { - presets: [ 'react-hmre' ] - } - } - ] - } -} - -// When inside Redux repo, prefer src to compiled version. -// You can safely delete these lines in your project. -var reduxSrc = path.join(__dirname, '..', '..', 'src') -var reduxNodeModules = path.join(__dirname, '..', '..', 'node_modules') -var fs = require('fs') -if (fs.existsSync(reduxSrc) && fs.existsSync(reduxNodeModules)) { - // Resolve Redux to source - module.exports.resolve = { alias: { 'redux': reduxSrc } } - // Our root .babelrc needs this flag for CommonJS output - process.env.BABEL_ENV = 'commonjs' - // Compile Redux from source - module.exports.module.loaders.push({ - test: /\.js$/, - loaders: [ 'babel' ], - include: reduxSrc, - }) -} From 25cfc5ab66d9614692c7ce01788ece234248aafa Mon Sep 17 00:00:00 2001 From: xulien Date: Wed, 17 Feb 2016 22:31:37 +0100 Subject: [PATCH 12/44] cleaning --- examples/async-with-routing/routes.js | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/examples/async-with-routing/routes.js b/examples/async-with-routing/routes.js index bf5287ffb5..1ffa529090 100644 --- a/examples/async-with-routing/routes.js +++ b/examples/async-with-routing/routes.js @@ -8,11 +8,11 @@ import Home from './components/Home' import Sample from './components/Sample' import Reddit from './containers/Reddit' -export default ( - - - - - - - ) +export default( + + + + + + +) From 41932f1f01439f18ee559cb740b459979f46b2e1 Mon Sep 17 00:00:00 2001 From: xulien Date: Wed, 17 Feb 2016 23:40:32 +0100 Subject: [PATCH 13/44] simplification of the routing --- examples/async-with-routing/components/Home.js | 13 ------------- examples/async-with-routing/components/Layout.js | 8 -------- examples/async-with-routing/components/Sample.js | 11 ----------- examples/async-with-routing/containers/Reddit.js | 13 ++++++++----- examples/async-with-routing/routes.js | 9 ++------- 5 files changed, 10 insertions(+), 44 deletions(-) delete mode 100644 examples/async-with-routing/components/Home.js delete mode 100644 examples/async-with-routing/components/Sample.js diff --git a/examples/async-with-routing/components/Home.js b/examples/async-with-routing/components/Home.js deleted file mode 100644 index d6cf0a2082..0000000000 --- a/examples/async-with-routing/components/Home.js +++ /dev/null @@ -1,13 +0,0 @@ -import React, { Component } from 'react' -import Link from 'react-router/lib/Link' - -export default class Home extends Component { - render() { - return ( -
-

Home page

-

Frontend

-
- ) - } -} diff --git a/examples/async-with-routing/components/Layout.js b/examples/async-with-routing/components/Layout.js index b39e09d2fa..021178b68e 100644 --- a/examples/async-with-routing/components/Layout.js +++ b/examples/async-with-routing/components/Layout.js @@ -1,17 +1,9 @@ import React, { Component } from 'react' -import Link from 'react-router/lib/Link' export default class Layout extends Component { render() { return (
- {this.props.children}
) diff --git a/examples/async-with-routing/components/Sample.js b/examples/async-with-routing/components/Sample.js deleted file mode 100644 index 2a88c3c77e..0000000000 --- a/examples/async-with-routing/components/Sample.js +++ /dev/null @@ -1,11 +0,0 @@ -import React, { Component } from 'react' - -export default class Sample extends Component { - render() { - return ( -
-

Sample page

-
- ) - } -} diff --git a/examples/async-with-routing/containers/Reddit.js b/examples/async-with-routing/containers/Reddit.js index 4ea254d627..f44cacc6ef 100644 --- a/examples/async-with-routing/containers/Reddit.js +++ b/examples/async-with-routing/containers/Reddit.js @@ -14,16 +14,19 @@ class Reddit extends Component { super(props) this.handleChange = this.handleChange.bind(this) this.handleRefreshClick = this.handleRefreshClick.bind(this) - this.rootPath = location.pathname.substring(0, location.pathname.lastIndexOf('/') + 1) } componentDidMount() { const { dispatch, selectedReddit, params } = this.props - dispatch(selectReddit(params.id)) - dispatch(fetchPostsIfNeeded(selectedReddit)) + if (params.id) { + dispatch(selectReddit(params.id)) + dispatch(fetchPostsIfNeeded(params.id)) + } else { + dispatch(fetchPostsIfNeeded(selectedReddit)) + } } - componentWillReceiveProps(nextProps) { + componentWillReceiveProps(nextProps) { const { dispatch, params } = this.props if ( nextProps.params.id !== params.id ) { dispatch(selectReddit(nextProps.params.id)) @@ -32,7 +35,7 @@ class Reddit extends Component { } handleChange(nextReddit) { - this.context.router.push(this.rootPath + nextReddit) + this.context.router.push(`/${nextReddit}`) } handleRefreshClick(e) { diff --git a/examples/async-with-routing/routes.js b/examples/async-with-routing/routes.js index 1ffa529090..f1bd518088 100644 --- a/examples/async-with-routing/routes.js +++ b/examples/async-with-routing/routes.js @@ -1,18 +1,13 @@ import React from 'react' import Route from 'react-router/lib/Route' import IndexRoute from 'react-router/lib/IndexRoute' -import Redirect from 'react-router/lib/Redirect' import Layout from './components/Layout' -import Home from './components/Home' -import Sample from './components/Sample' import Reddit from './containers/Reddit' export default( - - - - + + ) From 4dc3944c3e6f773a46700cbf212f795de5f60b21 Mon Sep 17 00:00:00 2001 From: xulien Date: Wed, 17 Feb 2016 23:46:58 +0100 Subject: [PATCH 14/44] title update --- examples/async-with-routing/index.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/async-with-routing/index.html b/examples/async-with-routing/index.html index b89496eae0..b9b31b6ff1 100644 --- a/examples/async-with-routing/index.html +++ b/examples/async-with-routing/index.html @@ -1,7 +1,7 @@ - Redux async example + Redux async with routing example
From 53a594bbb2bd551f3e39686f193d4e9e3cbef681 Mon Sep 17 00:00:00 2001 From: xulien Date: Thu, 18 Feb 2016 00:01:43 +0100 Subject: [PATCH 15/44] linting --- .../async-with-routing/containers/Reddit.js | 74 ++++++++++--------- 1 file changed, 40 insertions(+), 34 deletions(-) diff --git a/examples/async-with-routing/containers/Reddit.js b/examples/async-with-routing/containers/Reddit.js index f44cacc6ef..e11ecc7a80 100644 --- a/examples/async-with-routing/containers/Reddit.js +++ b/examples/async-with-routing/containers/Reddit.js @@ -1,6 +1,6 @@ -import React, { Component, PropTypes } from 'react' -import { connect } from 'react-redux' -import { selectReddit, fetchPostsIfNeeded, invalidateReddit } from '../actions' +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' @@ -17,7 +17,7 @@ class Reddit extends Component { } componentDidMount() { - const { dispatch, selectedReddit, params } = this.props + const {dispatch, selectedReddit, params} = this.props if (params.id) { dispatch(selectReddit(params.id)) dispatch(fetchPostsIfNeeded(params.id)) @@ -26,9 +26,9 @@ class Reddit extends Component { } } - componentWillReceiveProps(nextProps) { - const { dispatch, params } = this.props - if ( nextProps.params.id !== params.id ) { + componentWillReceiveProps(nextProps) { + const {dispatch, params} = this.props + if (nextProps.params.id !== params.id) { dispatch(selectReddit(nextProps.params.id)) dispatch(fetchPostsIfNeeded(nextProps.params.id)) } @@ -41,42 +41,39 @@ class Reddit extends Component { handleRefreshClick(e) { e.preventDefault() - const { dispatch, selectedReddit } = this.props + const {dispatch, selectedReddit} = this.props dispatch(invalidateReddit(selectedReddit)) dispatch(fetchPostsIfNeeded(selectedReddit)) } render() { - const { selectedReddit, posts, isFetching, lastUpdated } = this.props + const {selectedReddit, posts, isFetching, lastUpdated} = this.props const isEmpty = posts.length === 0 - return (
- +

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

{isEmpty - ? (isFetching - ?

Loading...

- :

Empty.

) - :
- -
-} + ? (isFetching ?

Loading...

:

Empty.

) + :
+ +
+ }
) } @@ -91,13 +88,22 @@ Reddit.propTypes = { } function mapStateToProps(state) { - const { selectedReddit, postsByReddit } = state - const { isFetching, lastUpdated, items: posts } = postsByReddit[selectedReddit] || { + const {selectedReddit, postsByReddit} = state + const { + isFetching, + lastUpdated, + items: posts + } = postsByReddit[selectedReddit] || { isFetching: true, items: [] } - return { selectedReddit, posts, isFetching, lastUpdated } + return { + selectedReddit, + posts, + isFetching, + lastUpdated + } } export default connect(mapStateToProps)(Reddit) From 2c42ba8df688de9029f12adb88f6b1e7cf473994 Mon Sep 17 00:00:00 2001 From: xulien Date: Thu, 18 Feb 2016 10:01:25 +0100 Subject: [PATCH 16/44] linting --- .../async-with-routing/containers/Reddit.js | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/examples/async-with-routing/containers/Reddit.js b/examples/async-with-routing/containers/Reddit.js index e11ecc7a80..3b47db46b0 100644 --- a/examples/async-with-routing/containers/Reddit.js +++ b/examples/async-with-routing/containers/Reddit.js @@ -1,6 +1,6 @@ -import React, {Component, PropTypes} from 'react' -import {connect} from 'react-redux' -import {selectReddit, fetchPostsIfNeeded, invalidateReddit} from '../actions' +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' @@ -17,7 +17,7 @@ class Reddit extends Component { } componentDidMount() { - const {dispatch, selectedReddit, params} = this.props + const { dispatch, selectedReddit, params } = this.props if (params.id) { dispatch(selectReddit(params.id)) dispatch(fetchPostsIfNeeded(params.id)) @@ -27,7 +27,7 @@ class Reddit extends Component { } componentWillReceiveProps(nextProps) { - const {dispatch, params} = this.props + const { dispatch, params } = this.props if (nextProps.params.id !== params.id) { dispatch(selectReddit(nextProps.params.id)) dispatch(fetchPostsIfNeeded(nextProps.params.id)) @@ -41,19 +41,19 @@ class Reddit extends Component { handleRefreshClick(e) { e.preventDefault() - const {dispatch, selectedReddit} = this.props + const { dispatch, selectedReddit } = this.props dispatch(invalidateReddit(selectedReddit)) dispatch(fetchPostsIfNeeded(selectedReddit)) } render() { - const {selectedReddit, posts, isFetching, lastUpdated} = this.props + const { selectedReddit, posts, isFetching, lastUpdated } = this.props const isEmpty = posts.length === 0 return (
+ options={ [ 'reactjs', 'frontend' ] } />

{lastUpdated && @@ -88,7 +88,7 @@ Reddit.propTypes = { } function mapStateToProps(state) { - const {selectedReddit, postsByReddit} = state + const { selectedReddit, postsByReddit } = state const { isFetching, lastUpdated, From d05f8d23fce53d2e0986bc7ffe20c957f8a05b92 Mon Sep 17 00:00:00 2001 From: xulien Date: Thu, 18 Feb 2016 10:34:25 +0100 Subject: [PATCH 17/44] name and description update --- examples/async-with-routing/package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/async-with-routing/package.json b/examples/async-with-routing/package.json index c82f3e19a2..2f24075473 100644 --- a/examples/async-with-routing/package.json +++ b/examples/async-with-routing/package.json @@ -1,7 +1,7 @@ { - "name": "redux-async-example", + "name": "redux-async-with-routing-example", "version": "0.0.0", - "description": "Redux async example", + "description": "Redux async with routing example", "scripts": { "start": "node server.js" }, From 0dc353fba3f3c8be341414d1fcd1b375311bd434 Mon Sep 17 00:00:00 2001 From: xulien Date: Thu, 18 Feb 2016 21:56:19 +0100 Subject: [PATCH 18/44] async-universal example --- examples/async-universal/.babelrc | 9 ++ examples/async-universal/client/index.js | 21 ++++ .../async-universal/common/actions/index.js | 64 ++++++++++ examples/async-universal/common/api/reddit.js | 12 ++ .../common/components/Layout.js | 11 ++ .../common/components/Picker.js | 29 +++++ .../common/components/Posts.js | 17 +++ .../common/containers/Reddit.js | 109 ++++++++++++++++++ .../async-universal/common/reducers/index.js | 61 ++++++++++ examples/async-universal/common/routes.js | 13 +++ .../common/store/configureStore.js | 22 ++++ examples/async-universal/package.json | 53 +++++++++ examples/async-universal/server/index.js | 2 + examples/async-universal/server/server.js | 87 ++++++++++++++ examples/async-universal/webpack.config.js | 49 ++++++++ 15 files changed, 559 insertions(+) create mode 100644 examples/async-universal/.babelrc create mode 100644 examples/async-universal/client/index.js create mode 100644 examples/async-universal/common/actions/index.js create mode 100644 examples/async-universal/common/api/reddit.js create mode 100644 examples/async-universal/common/components/Layout.js create mode 100644 examples/async-universal/common/components/Picker.js create mode 100644 examples/async-universal/common/components/Posts.js create mode 100644 examples/async-universal/common/containers/Reddit.js create mode 100644 examples/async-universal/common/reducers/index.js create mode 100644 examples/async-universal/common/routes.js create mode 100644 examples/async-universal/common/store/configureStore.js create mode 100644 examples/async-universal/package.json create mode 100644 examples/async-universal/server/index.js create mode 100644 examples/async-universal/server/server.js create mode 100644 examples/async-universal/webpack.config.js diff --git a/examples/async-universal/.babelrc b/examples/async-universal/.babelrc new file mode 100644 index 0000000000..b46ff35077 --- /dev/null +++ b/examples/async-universal/.babelrc @@ -0,0 +1,9 @@ +{ + "presets": ["es2015", "react"], + "plugins": ["transform-class-properties"], // needed to get local context.router + "env": { + "development": { + "presets": ["react-hmre"] + } + } +} 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/api/reddit.js b/examples/async-universal/common/api/reddit.js new file mode 100644 index 0000000000..73b53bb74f --- /dev/null +++ b/examples/async-universal/common/api/reddit.js @@ -0,0 +1,12 @@ +import { fetchPostsIfNeeded, selectReddit } from '../actions' + +export function fetchData(store, subreddit, callback) { + + Promise.all([ + store.dispatch(selectReddit(subreddit)), + store.dispatch(fetchPostsIfNeeded(subreddit)) + ]).then(() => { + callback() + }) + +} diff --git a/examples/async-universal/common/components/Layout.js b/examples/async-universal/common/components/Layout.js new file mode 100644 index 0000000000..021178b68e --- /dev/null +++ b/examples/async-universal/common/components/Layout.js @@ -0,0 +1,11 @@ +import React, { Component } from 'react' + +export default class Layout extends Component { + render() { + return ( +

+ {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..be78acb182 --- /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}

+ +
+ ) + } +} + +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 ( +
    + {this.props.posts.map((post, i) => +
  • {post.title}
  • + )} +
+ ) + } +} + +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..3b47db46b0 --- /dev/null +++ b/examples/async-universal/common/containers/Reddit.js @@ -0,0 +1,109 @@ +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 { + + static contextTypes = { + router: PropTypes.object + } + + constructor(props) { + super(props) + this.handleChange = this.handleChange.bind(this) + this.handleRefreshClick = this.handleRefreshClick.bind(this) + } + + componentDidMount() { + const { dispatch, selectedReddit, params } = this.props + if (params.id) { + dispatch(selectReddit(params.id)) + dispatch(fetchPostsIfNeeded(params.id)) + } else { + dispatch(fetchPostsIfNeeded(selectedReddit)) + } + } + + componentWillReceiveProps(nextProps) { + const { dispatch, params } = this.props + if (nextProps.params.id !== params.id) { + dispatch(selectReddit(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 && + + Refresh + + } +

+ {isEmpty + ? (isFetching ?

Loading...

:

Empty.

) + :
+ +
+ } +
+ ) + } +} + +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: true, + 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..f936cbced3 --- /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 = '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 diff --git a/examples/async-universal/common/routes.js b/examples/async-universal/common/routes.js new file mode 100644 index 0000000000..f1bd518088 --- /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 Layout from './components/Layout' +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..143d3f1b2d --- /dev/null +++ b/examples/async-universal/package.json @@ -0,0 +1,53 @@ +{ + "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-plugin-transform-class-properties": "^6.5.2", + "babel-preset-es2015": "^6.3.13", + "babel-preset-react": "^6.3.13", + "babel-preset-react-hmre": "^1.0.1", + "expect": "^1.6.0", + "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-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..f32110c4d4 --- /dev/null +++ b/examples/async-universal/server/server.js @@ -0,0 +1,87 @@ +/* eslint-disable no-console, no-use-before-define */ + +import path from 'path' +import Express from 'express' + +import webpack from 'webpack' +import webpackDevMiddleware from 'webpack-dev-middleware' +import webpackHotMiddleware from 'webpack-hot-middleware' +import config from '../webpack.config' + +import React from 'react' +import { renderToString } from 'react-dom/server' +import { Provider } from 'react-redux' +import { match, RouterContext } from 'react-router' + +import configureStore from '../common/store/configureStore' +import { fetchData } from '../common/api/reddit' +import routes from '../common/routes' + +const app = new Express() +const port = 3000 + +const 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 + }, (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 + const store = configureStore() + + // Query our API asynchronously + fetchData(store, req.path.slice(1), () => { + + const html = renderToString( + + + + ) + + const 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, (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..ae386ac46b --- /dev/null +++ b/examples/async-universal/webpack.config.js @@ -0,0 +1,49 @@ +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(), + new webpack.NoErrorsPlugin() + ], + module: { + loaders: [ + { + test: /\.js$/, + loaders: ['babel'], + exclude: /node_modules/, + include: __dirname + } + ] + } +} + + +// When inside Redux repo, prefer src to compiled version. +// You can safely delete these lines in your project. +var reduxSrc = path.join(__dirname, '..', '..', 'src') +var reduxNodeModules = path.join(__dirname, '..', '..', 'node_modules') +var fs = require('fs') +if (fs.existsSync(reduxSrc) && fs.existsSync(reduxNodeModules)) { + // Resolve Redux to source + module.exports.resolve = { alias: { 'redux': reduxSrc } } + // Our root .babelrc needs this flag for CommonJS output + process.env.BABEL_ENV = 'commonjs' + // Compile Redux from source + module.exports.module.loaders.push({ + test: /\.js$/, + loaders: ['babel'], + include: reduxSrc + }) +} From 30e4d8004c959ac3ff4b1299f4c1c94ec913e7cb Mon Sep 17 00:00:00 2001 From: xulien Date: Tue, 23 Feb 2016 10:57:10 +0100 Subject: [PATCH 19/44] generic index route --- examples/async-with-routing/components/Picker.js | 2 +- examples/async-with-routing/containers/Reddit.js | 15 +++++++++------ examples/async-with-routing/reducers/index.js | 4 ++-- 3 files changed, 12 insertions(+), 9 deletions(-) diff --git a/examples/async-with-routing/components/Picker.js b/examples/async-with-routing/components/Picker.js index be78acb182..2f469af982 100644 --- a/examples/async-with-routing/components/Picker.js +++ b/examples/async-with-routing/components/Picker.js @@ -6,7 +6,7 @@ export default class Picker extends Component { return ( -

{value}

+

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

onChange(e.target.value)} value={value}> {options.map(option => diff --git a/examples/async-universal/common/containers/Reddit.js b/examples/async-universal/common/containers/Reddit.js index 3b47db46b0..398865abab 100644 --- a/examples/async-universal/common/containers/Reddit.js +++ b/examples/async-universal/common/containers/Reddit.js @@ -17,21 +17,23 @@ class Reddit extends Component { } componentDidMount() { - const { dispatch, selectedReddit, params } = this.props + const { dispatch, params } = this.props if (params.id) { dispatch(selectReddit(params.id)) dispatch(fetchPostsIfNeeded(params.id)) - } else { - dispatch(fetchPostsIfNeeded(selectedReddit)) } } componentWillReceiveProps(nextProps) { const { dispatch, params } = this.props + if (nextProps.params.id !== params.id) { dispatch(selectReddit(nextProps.params.id)) - dispatch(fetchPostsIfNeeded(nextProps.params.id)) + if (nextProps.params.id) { + dispatch(fetchPostsIfNeeded(nextProps.params.id)) + } } + } handleChange(nextReddit) { @@ -53,7 +55,7 @@ class Reddit extends Component {
+ options={ [ '', 'reactjs', 'frontend' ] } />

{lastUpdated && @@ -61,7 +63,7 @@ class Reddit extends Component { {' '} } - {!isFetching && + {!isFetching && selectedReddit && Refresh @@ -94,7 +96,7 @@ function mapStateToProps(state) { lastUpdated, items: posts } = postsByReddit[selectedReddit] || { - isFetching: true, + isFetching: false, items: [] } diff --git a/examples/async-universal/common/reducers/index.js b/examples/async-universal/common/reducers/index.js index f936cbced3..d6836ee0ff 100644 --- a/examples/async-universal/common/reducers/index.js +++ b/examples/async-universal/common/reducers/index.js @@ -4,10 +4,10 @@ import { REQUEST_POSTS, RECEIVE_POSTS } from '../actions' -function selectedReddit(state = 'reactjs', action) { +function selectedReddit(state = '', action) { switch (action.type) { case SELECT_REDDIT: - return action.reddit + return action.reddit || '' default: return state } From c91dccb70369d61259765cb08e3d239bda4b7bf1 Mon Sep 17 00:00:00 2001 From: xulien Date: Fri, 11 Mar 2016 09:33:31 +0100 Subject: [PATCH 22/44] Layout.js removed --- examples/async-universal/common/components/Layout.js | 11 ----------- examples/async-universal/common/routes.js | 4 +--- 2 files changed, 1 insertion(+), 14 deletions(-) delete mode 100644 examples/async-universal/common/components/Layout.js diff --git a/examples/async-universal/common/components/Layout.js b/examples/async-universal/common/components/Layout.js deleted file mode 100644 index 021178b68e..0000000000 --- a/examples/async-universal/common/components/Layout.js +++ /dev/null @@ -1,11 +0,0 @@ -import React, { Component } from 'react' - -export default class Layout extends Component { - render() { - return ( -

- ) - } -} diff --git a/examples/async-universal/common/routes.js b/examples/async-universal/common/routes.js index f1bd518088..4b04b70ffc 100644 --- a/examples/async-universal/common/routes.js +++ b/examples/async-universal/common/routes.js @@ -2,12 +2,10 @@ import React from 'react' import Route from 'react-router/lib/Route' import IndexRoute from 'react-router/lib/IndexRoute' -import Layout from './components/Layout' import Reddit from './containers/Reddit' export default( - - + ) From 71d55cf12ae38465c994afbdb5ea9983fe89ac0e Mon Sep 17 00:00:00 2001 From: xulien Date: Fri, 11 Mar 2016 09:56:53 +0100 Subject: [PATCH 23/44] intermediate refactoring --- examples/async-universal/common/containers/Reddit.js | 8 -------- examples/async-universal/server/server.js | 4 +++- 2 files changed, 3 insertions(+), 9 deletions(-) diff --git a/examples/async-universal/common/containers/Reddit.js b/examples/async-universal/common/containers/Reddit.js index 398865abab..c267536463 100644 --- a/examples/async-universal/common/containers/Reddit.js +++ b/examples/async-universal/common/containers/Reddit.js @@ -16,14 +16,6 @@ class Reddit extends Component { 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 diff --git a/examples/async-universal/server/server.js b/examples/async-universal/server/server.js index f32110c4d4..9d534faf2a 100644 --- a/examples/async-universal/server/server.js +++ b/examples/async-universal/server/server.js @@ -40,8 +40,10 @@ function handleRender(req, res) { // Create a new Redux store instance const store = configureStore() + const { params } = renderProps + // Query our API asynchronously - fetchData(store, req.path.slice(1), () => { + fetchData(store, params.id, () => { const html = renderToString( From 9633ecff23ab7de55be0d61f0ad6844dbb432027 Mon Sep 17 00:00:00 2001 From: xulien Date: Fri, 11 Mar 2016 10:53:45 +0100 Subject: [PATCH 24/44] cleaning --- examples/async-universal/common/routes.js | 1 - examples/async-universal/server/server.js | 4 ++-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/examples/async-universal/common/routes.js b/examples/async-universal/common/routes.js index 4b04b70ffc..8a658be590 100644 --- a/examples/async-universal/common/routes.js +++ b/examples/async-universal/common/routes.js @@ -1,6 +1,5 @@ import React from 'react' import Route from 'react-router/lib/Route' -import IndexRoute from 'react-router/lib/IndexRoute' import Reddit from './containers/Reddit' diff --git a/examples/async-universal/server/server.js b/examples/async-universal/server/server.js index 9d534faf2a..ad85068204 100644 --- a/examples/async-universal/server/server.js +++ b/examples/async-universal/server/server.js @@ -41,13 +41,13 @@ function handleRender(req, res) { const store = configureStore() const { params } = renderProps - + // Query our API asynchronously fetchData(store, params.id, () => { const html = renderToString( - + ) From c8bfd67af8f74a83af6d299b3e6c5a728bb5df7d Mon Sep 17 00:00:00 2001 From: xulien Date: Fri, 11 Mar 2016 14:40:08 +0100 Subject: [PATCH 25/44] move fetchdata into container --- examples/async-universal/common/api/reddit.js | 14 -------------- .../async-universal/common/containers/Reddit.js | 12 ++++++++++++ examples/async-universal/server/server.js | 6 ++++-- 3 files changed, 16 insertions(+), 16 deletions(-) delete mode 100644 examples/async-universal/common/api/reddit.js diff --git a/examples/async-universal/common/api/reddit.js b/examples/async-universal/common/api/reddit.js deleted file mode 100644 index d6b0baa9aa..0000000000 --- a/examples/async-universal/common/api/reddit.js +++ /dev/null @@ -1,14 +0,0 @@ -import { fetchPostsIfNeeded, selectReddit } from '../actions' - -export function fetchData(store, subreddit, callback) { - - if (!subreddit) return callback() - - Promise.all([ - store.dispatch(selectReddit(subreddit)), - store.dispatch(fetchPostsIfNeeded(subreddit)) - ]).then(() => { - callback() - }) - -} diff --git a/examples/async-universal/common/containers/Reddit.js b/examples/async-universal/common/containers/Reddit.js index c267536463..7bb16a1b7a 100644 --- a/examples/async-universal/common/containers/Reddit.js +++ b/examples/async-universal/common/containers/Reddit.js @@ -16,6 +16,18 @@ class Reddit extends Component { this.handleRefreshClick = this.handleRefreshClick.bind(this) } + static fetchData(dispatch, params) { + const subreddit = params.id + if (subreddit) { + return Promise.all([ + dispatch(selectReddit(subreddit)), + dispatch(fetchPostsIfNeeded(subreddit)) + ]) + } else { + return Promise.resolve() + } + } + componentWillReceiveProps(nextProps) { const { dispatch, params } = this.props diff --git a/examples/async-universal/server/server.js b/examples/async-universal/server/server.js index ad85068204..03bc088f9c 100644 --- a/examples/async-universal/server/server.js +++ b/examples/async-universal/server/server.js @@ -40,10 +40,12 @@ function handleRender(req, res) { // Create a new Redux store instance const store = configureStore() - const { params } = renderProps + // Grab static fetchData + const { components, params } = renderProps + const fetchData = components[ components.length - 1 ].fetchData // Query our API asynchronously - fetchData(store, params.id, () => { + fetchData(store.dispatch, params).then(() => { const html = renderToString( From ad054bb24e863617fe2b120d25d41e980038c19b Mon Sep 17 00:00:00 2001 From: xulien Date: Fri, 11 Mar 2016 21:10:35 +0100 Subject: [PATCH 26/44] cleaning --- examples/async-universal/server/server.js | 1 - 1 file changed, 1 deletion(-) diff --git a/examples/async-universal/server/server.js b/examples/async-universal/server/server.js index 03bc088f9c..5de0f2934e 100644 --- a/examples/async-universal/server/server.js +++ b/examples/async-universal/server/server.js @@ -14,7 +14,6 @@ import { Provider } from 'react-redux' import { match, RouterContext } from 'react-router' import configureStore from '../common/store/configureStore' -import { fetchData } from '../common/api/reddit' import routes from '../common/routes' const app = new Express() From a1f2d631cf4b9013cba7ddfc9d16597034713958 Mon Sep 17 00:00:00 2001 From: xulien Date: Tue, 12 Apr 2016 09:48:46 +0200 Subject: [PATCH 27/44] remove transform-class-properties --- examples/async-universal/.babelrc | 1 - examples/async-universal/common/containers/Reddit.js | 8 ++++---- examples/async-universal/package.json | 1 - examples/async-with-routing/.babelrc | 1 - examples/async-with-routing/containers/Reddit.js | 8 ++++---- examples/async-with-routing/package.json | 1 - 6 files changed, 8 insertions(+), 12 deletions(-) diff --git a/examples/async-universal/.babelrc b/examples/async-universal/.babelrc index b46ff35077..d0962f5695 100644 --- a/examples/async-universal/.babelrc +++ b/examples/async-universal/.babelrc @@ -1,6 +1,5 @@ { "presets": ["es2015", "react"], - "plugins": ["transform-class-properties"], // needed to get local context.router "env": { "development": { "presets": ["react-hmre"] diff --git a/examples/async-universal/common/containers/Reddit.js b/examples/async-universal/common/containers/Reddit.js index 7bb16a1b7a..09326af585 100644 --- a/examples/async-universal/common/containers/Reddit.js +++ b/examples/async-universal/common/containers/Reddit.js @@ -6,10 +6,6 @@ import Posts from '../components/Posts' class Reddit extends Component { - static contextTypes = { - router: PropTypes.object - } - constructor(props) { super(props) this.handleChange = this.handleChange.bind(this) @@ -85,6 +81,10 @@ class Reddit extends Component { } } +Reddit.contextTypes = { + router: PropTypes.object +} + Reddit.propTypes = { selectedReddit: PropTypes.string.isRequired, posts: PropTypes.array.isRequired, diff --git a/examples/async-universal/package.json b/examples/async-universal/package.json index 143d3f1b2d..66684c772c 100644 --- a/examples/async-universal/package.json +++ b/examples/async-universal/package.json @@ -40,7 +40,6 @@ "devDependencies": { "babel-core": "^6.3.15", "babel-loader": "^6.2.0", - "babel-plugin-transform-class-properties": "^6.5.2", "babel-preset-es2015": "^6.3.13", "babel-preset-react": "^6.3.13", "babel-preset-react-hmre": "^1.0.1", diff --git a/examples/async-with-routing/.babelrc b/examples/async-with-routing/.babelrc index b46ff35077..d0962f5695 100644 --- a/examples/async-with-routing/.babelrc +++ b/examples/async-with-routing/.babelrc @@ -1,6 +1,5 @@ { "presets": ["es2015", "react"], - "plugins": ["transform-class-properties"], // needed to get local context.router "env": { "development": { "presets": ["react-hmre"] diff --git a/examples/async-with-routing/containers/Reddit.js b/examples/async-with-routing/containers/Reddit.js index e31b7001c4..8cff005988 100644 --- a/examples/async-with-routing/containers/Reddit.js +++ b/examples/async-with-routing/containers/Reddit.js @@ -6,10 +6,6 @@ import Posts from '../components/Posts' class Reddit extends Component { - static contextTypes = { - router: PropTypes.object - } - constructor(props) { super(props) this.handleChange = this.handleChange.bind(this) @@ -82,6 +78,10 @@ class Reddit extends Component { } } +Reddit.contextTypes = { + router: PropTypes.object +} + Reddit.propTypes = { selectedReddit: PropTypes.string.isRequired, posts: PropTypes.array.isRequired, diff --git a/examples/async-with-routing/package.json b/examples/async-with-routing/package.json index 2f24075473..805503889b 100644 --- a/examples/async-with-routing/package.json +++ b/examples/async-with-routing/package.json @@ -39,7 +39,6 @@ "devDependencies": { "babel-core": "^6.3.15", "babel-loader": "^6.2.0", - "babel-plugin-transform-class-properties": "^6.5.2", "babel-preset-es2015": "^6.3.13", "babel-preset-react": "^6.3.13", "babel-preset-react-hmre": "^1.0.1", From 545b2888c6bbaac6d26ff6e3b8ce868e47f2485f Mon Sep 17 00:00:00 2001 From: xulien Date: Tue, 12 Apr 2016 09:52:27 +0200 Subject: [PATCH 28/44] remove static keyword --- .../common/containers/Reddit.js | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/examples/async-universal/common/containers/Reddit.js b/examples/async-universal/common/containers/Reddit.js index 09326af585..c678a0b4c2 100644 --- a/examples/async-universal/common/containers/Reddit.js +++ b/examples/async-universal/common/containers/Reddit.js @@ -12,18 +12,6 @@ class Reddit extends Component { this.handleRefreshClick = this.handleRefreshClick.bind(this) } - static fetchData(dispatch, params) { - const subreddit = params.id - if (subreddit) { - return Promise.all([ - dispatch(selectReddit(subreddit)), - dispatch(fetchPostsIfNeeded(subreddit)) - ]) - } else { - return Promise.resolve() - } - } - componentWillReceiveProps(nextProps) { const { dispatch, params } = this.props @@ -81,6 +69,18 @@ class Reddit extends Component { } } +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 } From a489327c0669804870670cf0b29413eda2f3afdf Mon Sep 17 00:00:00 2001 From: xulien Date: Tue, 12 Apr 2016 09:53:42 +0200 Subject: [PATCH 29/44] missing space --- examples/async-universal/common/routes.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/async-universal/common/routes.js b/examples/async-universal/common/routes.js index 8a658be590..9ddf6cf240 100644 --- a/examples/async-universal/common/routes.js +++ b/examples/async-universal/common/routes.js @@ -3,7 +3,7 @@ import Route from 'react-router/lib/Route' import Reddit from './containers/Reddit' -export default( +export default ( From d4604bb3a5c3922f1dead96a0a19549333d36bec Mon Sep 17 00:00:00 2001 From: xulien Date: Tue, 12 Apr 2016 09:57:13 +0200 Subject: [PATCH 30/44] typo --- examples/async-universal/server/server.js | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/examples/async-universal/server/server.js b/examples/async-universal/server/server.js index 5de0f2934e..7580a1f8d2 100644 --- a/examples/async-universal/server/server.js +++ b/examples/async-universal/server/server.js @@ -26,10 +26,7 @@ app.use(webpackHotMiddleware(compiler)) app.use(handleRender) function handleRender(req, res) { - match({ - routes, - location: req.url - }, (error, redirectLocation, renderProps) => { + match({ routes, location: req.url }, (error, redirectLocation, renderProps) => { if (error) { res.status(500).send(error.message) } else if (redirectLocation) { From 110489eeb92ae46bc3941679a0ecae91b1a3435b Mon Sep 17 00:00:00 2001 From: xulien Date: Tue, 12 Apr 2016 09:58:31 +0200 Subject: [PATCH 31/44] remove unused import --- examples/async-universal/server/server.js | 1 - 1 file changed, 1 deletion(-) diff --git a/examples/async-universal/server/server.js b/examples/async-universal/server/server.js index 7580a1f8d2..685ba20abb 100644 --- a/examples/async-universal/server/server.js +++ b/examples/async-universal/server/server.js @@ -1,6 +1,5 @@ /* eslint-disable no-console, no-use-before-define */ -import path from 'path' import Express from 'express' import webpack from 'webpack' From 5b96f84d845149caa5041f433b692db97612203e Mon Sep 17 00:00:00 2001 From: xulien Date: Tue, 12 Apr 2016 10:01:34 +0200 Subject: [PATCH 32/44] Remove hacks to alias Redux to src folder in examples --- examples/async-universal/webpack.config.js | 19 ------------------- examples/async-with-routing/webpack.config.js | 19 ------------------- 2 files changed, 38 deletions(-) diff --git a/examples/async-universal/webpack.config.js b/examples/async-universal/webpack.config.js index ae386ac46b..b66ab632dc 100644 --- a/examples/async-universal/webpack.config.js +++ b/examples/async-universal/webpack.config.js @@ -28,22 +28,3 @@ module.exports = { ] } } - - -// When inside Redux repo, prefer src to compiled version. -// You can safely delete these lines in your project. -var reduxSrc = path.join(__dirname, '..', '..', 'src') -var reduxNodeModules = path.join(__dirname, '..', '..', 'node_modules') -var fs = require('fs') -if (fs.existsSync(reduxSrc) && fs.existsSync(reduxNodeModules)) { - // Resolve Redux to source - module.exports.resolve = { alias: { 'redux': reduxSrc } } - // Our root .babelrc needs this flag for CommonJS output - process.env.BABEL_ENV = 'commonjs' - // Compile Redux from source - module.exports.module.loaders.push({ - test: /\.js$/, - loaders: ['babel'], - include: reduxSrc - }) -} diff --git a/examples/async-with-routing/webpack.config.js b/examples/async-with-routing/webpack.config.js index f71daecee2..f41f81ecd9 100644 --- a/examples/async-with-routing/webpack.config.js +++ b/examples/async-with-routing/webpack.config.js @@ -28,22 +28,3 @@ module.exports = { ] } } - - -// When inside Redux repo, prefer src to compiled version. -// You can safely delete these lines in your project. -var reduxSrc = path.join(__dirname, '..', '..', 'src') -var reduxNodeModules = path.join(__dirname, '..', '..', 'node_modules') -var fs = require('fs') -if (fs.existsSync(reduxSrc) && fs.existsSync(reduxNodeModules)) { - // Resolve Redux to source - module.exports.resolve = { alias: { 'redux': reduxSrc } } - // Our root .babelrc needs this flag for CommonJS output - process.env.BABEL_ENV = 'commonjs' - // Compile Redux from source - module.exports.module.loaders.push({ - test: /\.js$/, - loaders: ['babel'], - include: reduxSrc - }) -} From 50ef70c93f0d3489685336d9a2a4896fb673922e Mon Sep 17 00:00:00 2001 From: xulien Date: Tue, 12 Apr 2016 10:08:01 +0200 Subject: [PATCH 33/44] Per-file ESLint exceptions removed --- examples/async-universal/server/server.js | 3 --- 1 file changed, 3 deletions(-) diff --git a/examples/async-universal/server/server.js b/examples/async-universal/server/server.js index 685ba20abb..e23c6785b8 100644 --- a/examples/async-universal/server/server.js +++ b/examples/async-universal/server/server.js @@ -1,12 +1,9 @@ -/* eslint-disable no-console, no-use-before-define */ - import Express from 'express' import webpack from 'webpack' import webpackDevMiddleware from 'webpack-dev-middleware' import webpackHotMiddleware from 'webpack-hot-middleware' import config from '../webpack.config' - import React from 'react' import { renderToString } from 'react-dom/server' import { Provider } from 'react-redux' From bf3b17a8b93dedfefbff5e5804173b3132fcc371 Mon Sep 17 00:00:00 2001 From: xulien Date: Tue, 12 Apr 2016 13:12:22 +0200 Subject: [PATCH 34/44] Layout.js renamed to App.js --- examples/async-with-routing/components/App.js | 16 ++++++++++++++++ examples/async-with-routing/components/Layout.js | 11 ----------- examples/async-with-routing/routes.js | 4 ++-- 3 files changed, 18 insertions(+), 13 deletions(-) create mode 100644 examples/async-with-routing/components/App.js delete mode 100644 examples/async-with-routing/components/Layout.js 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 ( + + ) + } +} diff --git a/examples/async-with-routing/components/Layout.js b/examples/async-with-routing/components/Layout.js deleted file mode 100644 index 021178b68e..0000000000 --- a/examples/async-with-routing/components/Layout.js +++ /dev/null @@ -1,11 +0,0 @@ -import React, { Component } from 'react' - -export default class Layout extends Component { - render() { - return ( -
- {this.props.children} -
- ) - } -} diff --git a/examples/async-with-routing/routes.js b/examples/async-with-routing/routes.js index f1bd518088..2d1dd4abe8 100644 --- a/examples/async-with-routing/routes.js +++ b/examples/async-with-routing/routes.js @@ -2,11 +2,11 @@ import React from 'react' import Route from 'react-router/lib/Route' import IndexRoute from 'react-router/lib/IndexRoute' -import Layout from './components/Layout' +import App from './components/App' import Reddit from './containers/Reddit' export default( - + From 6890c4af479299d1d24292c3e96b52e64ed4f3ba Mon Sep 17 00:00:00 2001 From: xulien Date: Tue, 12 Apr 2016 13:18:45 +0200 Subject: [PATCH 35/44] Remove NoErrorsPlugin --- examples/async-with-routing/webpack.config.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/examples/async-with-routing/webpack.config.js b/examples/async-with-routing/webpack.config.js index f41f81ecd9..35062a810d 100644 --- a/examples/async-with-routing/webpack.config.js +++ b/examples/async-with-routing/webpack.config.js @@ -14,8 +14,7 @@ module.exports = { }, plugins: [ new webpack.optimize.OccurenceOrderPlugin(), - new webpack.HotModuleReplacementPlugin(), - new webpack.NoErrorsPlugin() + new webpack.HotModuleReplacementPlugin() ], module: { loaders: [ From 38105caf9bd0ad5ea221f4e82eb2aafb8f47dbf2 Mon Sep 17 00:00:00 2001 From: xulien Date: Tue, 12 Apr 2016 13:18:55 +0200 Subject: [PATCH 36/44] Remove NoErrorsPlugin --- examples/async-universal/webpack.config.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/examples/async-universal/webpack.config.js b/examples/async-universal/webpack.config.js index b66ab632dc..d82cdd4836 100644 --- a/examples/async-universal/webpack.config.js +++ b/examples/async-universal/webpack.config.js @@ -14,8 +14,7 @@ module.exports = { }, plugins: [ new webpack.optimize.OccurenceOrderPlugin(), - new webpack.HotModuleReplacementPlugin(), - new webpack.NoErrorsPlugin() + new webpack.HotModuleReplacementPlugin() ], module: { loaders: [ From a424585f8499e685726a22cdc852168a1c681887 Mon Sep 17 00:00:00 2001 From: xulien Date: Tue, 12 Apr 2016 14:11:34 +0200 Subject: [PATCH 37/44] compile redux from source is needed here --- examples/async-universal/webpack.config.js | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/examples/async-universal/webpack.config.js b/examples/async-universal/webpack.config.js index d82cdd4836..b426cb1cba 100644 --- a/examples/async-universal/webpack.config.js +++ b/examples/async-universal/webpack.config.js @@ -27,3 +27,22 @@ module.exports = { ] } } + + +// When inside Redux repo, prefer src to compiled version. +// You can safely delete these lines in your project. +var reduxSrc = path.join(__dirname, '..', '..', 'src') +var reduxNodeModules = path.join(__dirname, '..', '..', 'node_modules') +var fs = require('fs') +if (fs.existsSync(reduxSrc) && fs.existsSync(reduxNodeModules)) { + // Resolve Redux to source + module.exports.resolve = { alias: { 'redux': reduxSrc } } + // Our root .babelrc needs this flag for CommonJS output + process.env.BABEL_ENV = 'commonjs' + // Compile Redux from source + module.exports.module.loaders.push({ + test: /\.js$/, + loaders: ['babel'], + include: reduxSrc + }) +} \ No newline at end of file From 6a5d6e620ca138386d557d8f7dce8069f3b03ea5 Mon Sep 17 00:00:00 2001 From: xulien Date: Tue, 12 Apr 2016 14:12:26 +0200 Subject: [PATCH 38/44] App component with kind of header added --- .../async-universal/common/components/App.js | 16 ++++++++++++++++ examples/async-universal/common/routes.js | 5 ++++- 2 files changed, 20 insertions(+), 1 deletion(-) create mode 100644 examples/async-universal/common/components/App.js 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/routes.js b/examples/async-universal/common/routes.js index 9ddf6cf240..0d8bb3717c 100644 --- a/examples/async-universal/common/routes.js +++ b/examples/async-universal/common/routes.js @@ -1,10 +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 ( - + + ) From a56fcbb7e14980e38d9903660c012c4ae219b351 Mon Sep 17 00:00:00 2001 From: xulien Date: Tue, 12 Apr 2016 14:41:58 +0200 Subject: [PATCH 39/44] indentation --- examples/async-universal/server/server.js | 31 +++++++++++------------ 1 file changed, 15 insertions(+), 16 deletions(-) diff --git a/examples/async-universal/server/server.js b/examples/async-universal/server/server.js index e23c6785b8..8f7a426453 100644 --- a/examples/async-universal/server/server.js +++ b/examples/async-universal/server/server.js @@ -28,27 +28,26 @@ function handleRender(req, res) { } else if (redirectLocation) { res.redirect(302, redirectLocation.pathname + redirectLocation.search) } else if (renderProps) { + // Create a new Redux store instance + const store = configureStore() - // Create a new Redux store instance - const store = configureStore() + // Grab static fetchData + const { components, params } = renderProps + const fetchData = components[ components.length - 1 ].fetchData - // Grab static fetchData - const { components, params } = renderProps - const fetchData = components[ components.length - 1 ].fetchData + // Query our API asynchronously + fetchData(store.dispatch, params).then(() => { - // Query our API asynchronously - fetchData(store.dispatch, params).then(() => { + const html = renderToString( + + + + ) - const html = renderToString( - - - - ) + const finalState = store.getState() - const finalState = store.getState() - - res.status(200).send(renderFullPage(html, finalState)) - }) + res.status(200).send(renderFullPage(html, finalState)) + }) } else { res.status(404).send('Not found') From 2f3cad85712141740a14a80aca48c33e2bbd6f83 Mon Sep 17 00:00:00 2001 From: xulien Date: Tue, 12 Apr 2016 15:31:42 +0200 Subject: [PATCH 40/44] babel-preset-react-hmre removed --- examples/async-universal/.babelrc | 7 +------ examples/async-universal/package.json | 2 +- examples/async-universal/webpack.config.js | 19 ------------------- 3 files changed, 2 insertions(+), 26 deletions(-) diff --git a/examples/async-universal/.babelrc b/examples/async-universal/.babelrc index d0962f5695..86c445f545 100644 --- a/examples/async-universal/.babelrc +++ b/examples/async-universal/.babelrc @@ -1,8 +1,3 @@ { - "presets": ["es2015", "react"], - "env": { - "development": { - "presets": ["react-hmre"] - } - } + "presets": ["es2015", "react"] } diff --git a/examples/async-universal/package.json b/examples/async-universal/package.json index 66684c772c..123579a793 100644 --- a/examples/async-universal/package.json +++ b/examples/async-universal/package.json @@ -42,9 +42,9 @@ "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", "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/webpack.config.js b/examples/async-universal/webpack.config.js index b426cb1cba..5aaa90d519 100644 --- a/examples/async-universal/webpack.config.js +++ b/examples/async-universal/webpack.config.js @@ -26,23 +26,4 @@ module.exports = { } ] } -} - - -// When inside Redux repo, prefer src to compiled version. -// You can safely delete these lines in your project. -var reduxSrc = path.join(__dirname, '..', '..', 'src') -var reduxNodeModules = path.join(__dirname, '..', '..', 'node_modules') -var fs = require('fs') -if (fs.existsSync(reduxSrc) && fs.existsSync(reduxNodeModules)) { - // Resolve Redux to source - module.exports.resolve = { alias: { 'redux': reduxSrc } } - // Our root .babelrc needs this flag for CommonJS output - process.env.BABEL_ENV = 'commonjs' - // Compile Redux from source - module.exports.module.loaders.push({ - test: /\.js$/, - loaders: ['babel'], - include: reduxSrc - }) } \ No newline at end of file From 35469d45317c5c2930f32206e20dd92127fb44af Mon Sep 17 00:00:00 2001 From: xulien Date: Thu, 14 Apr 2016 07:15:44 +0200 Subject: [PATCH 41/44] es5 version for the sake of consistency --- examples/async-universal/server/server.js | 42 +++++++++++------------ 1 file changed, 20 insertions(+), 22 deletions(-) diff --git a/examples/async-universal/server/server.js b/examples/async-universal/server/server.js index 8f7a426453..7d00807214 100644 --- a/examples/async-universal/server/server.js +++ b/examples/async-universal/server/server.js @@ -1,42 +1,40 @@ -import Express from 'express' +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') -import webpack from 'webpack' -import webpackDevMiddleware from 'webpack-dev-middleware' -import webpackHotMiddleware from 'webpack-hot-middleware' -import config from '../webpack.config' -import React from 'react' -import { renderToString } from 'react-dom/server' -import { Provider } from 'react-redux' -import { match, RouterContext } from 'react-router' +var configureStore = require('../common/store/configureStore').default +var routes = require('../common/routes').default -import configureStore from '../common/store/configureStore' -import routes from '../common/routes' +var app = new (require('express'))() +var port = 3000 -const app = new Express() -const port = 3000 - -const compiler = webpack(config) +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 }, (error, redirectLocation, renderProps) => { + 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 - const store = configureStore() + var store = configureStore() // Grab static fetchData - const { components, params } = renderProps - const fetchData = components[ components.length - 1 ].fetchData + var fetchData = renderProps.components[ renderProps.components.length - 1 ].fetchData // Query our API asynchronously - fetchData(store.dispatch, params).then(() => { + fetchData(store.dispatch, renderProps.params).then(() => { const html = renderToString( @@ -44,7 +42,7 @@ function handleRender(req, res) { ) - const finalState = store.getState() + var finalState = store.getState() res.status(200).send(renderFullPage(html, finalState)) }) @@ -73,7 +71,7 @@ function renderFullPage(html, initialState) { ` } -app.listen(port, (error) => { +app.listen(port, function(error) { if (error) { console.error(error) } else { From 0a5ea4828f3c4ecf272347866d83e324e8cf498c Mon Sep 17 00:00:00 2001 From: Dan Abramov Date: Sun, 12 Jun 2016 01:21:21 +0100 Subject: [PATCH 42/44] Start updating async example (wip) --- examples/async/.babelrc | 6 +-- examples/async/components/Picker.js | 29 ------------- examples/async/components/Posts.js | 17 -------- examples/async/{server.js => devServer.js} | 0 examples/async/package.json | 28 ++++--------- examples/async/{ => src}/actions/index.js | 17 +++----- .../{containers => src/components}/App.js | 37 +++++++++------- examples/async/src/components/Picker.js | 25 +++++++++++ examples/async/src/components/Posts.js | 15 +++++++ examples/async/src/components/Root.js | 15 +++++++ examples/async/{ => src}/index.js | 7 +--- examples/async/{ => src}/reducers/index.js | 42 +++++++++---------- .../async/{ => src}/store/configureStore.js | 11 +++-- ...ebpack.config.js => webpack.dev.config.js} | 3 +- examples/async/webpack.prod.config.js | 28 +++++++++++++ 15 files changed, 147 insertions(+), 133 deletions(-) delete mode 100644 examples/async/components/Picker.js delete mode 100644 examples/async/components/Posts.js rename examples/async/{server.js => devServer.js} (100%) rename examples/async/{ => src}/actions/index.js (74%) rename examples/async/{containers => src/components}/App.js (70%) create mode 100644 examples/async/src/components/Picker.js create mode 100644 examples/async/src/components/Posts.js create mode 100644 examples/async/src/components/Root.js rename examples/async/{ => src}/index.js (62%) rename examples/async/{ => src}/reducers/index.js (58%) rename examples/async/{ => src}/store/configureStore.js (62%) rename examples/async/{webpack.config.js => webpack.dev.config.js} (88%) create mode 100644 examples/async/webpack.prod.config.js diff --git a/examples/async/.babelrc b/examples/async/.babelrc index d0962f5695..47c9aceb7e 100644 --- a/examples/async/.babelrc +++ b/examples/async/.babelrc @@ -1,8 +1,4 @@ { "presets": ["es2015", "react"], - "env": { - "development": { - "presets": ["react-hmre"] - } - } + "plugins": ["transform-object-rest-spread"] } diff --git a/examples/async/components/Picker.js b/examples/async/components/Picker.js deleted file mode 100644 index be78acb182..0000000000 --- a/examples/async/components/Picker.js +++ /dev/null @@ -1,29 +0,0 @@ -import React, { Component, PropTypes } from 'react' - -export default class Picker extends Component { - render() { - const { value, onChange, options } = this.props - - return ( - -

{value}

- -
- ) - } -} - -Picker.propTypes = { - options: PropTypes.arrayOf( - PropTypes.string.isRequired - ).isRequired, - value: PropTypes.string.isRequired, - onChange: PropTypes.func.isRequired -} diff --git a/examples/async/components/Posts.js b/examples/async/components/Posts.js deleted file mode 100644 index dd3285dab9..0000000000 --- a/examples/async/components/Posts.js +++ /dev/null @@ -1,17 +0,0 @@ -import React, { PropTypes, Component } from 'react' - -export default class Posts extends Component { - render() { - return ( -
    - {this.props.posts.map((post, i) => -
  • {post.title}
  • - )} -
- ) - } -} - -Posts.propTypes = { - posts: PropTypes.array.isRequired -} diff --git a/examples/async/server.js b/examples/async/devServer.js similarity index 100% rename from examples/async/server.js rename to examples/async/devServer.js diff --git a/examples/async/package.json b/examples/async/package.json index 6eb76c1b18..4bb279555c 100644 --- a/examples/async/package.json +++ b/examples/async/package.json @@ -1,46 +1,32 @@ { "name": "redux-async-example", - "version": "0.0.0", "description": "Redux async example", "scripts": { - "start": "node server.js" + "start": "node devServer.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", - "redux": "^3.2.1", + "react": "^15.1.0", + "react-dom": "^15.1.0", + "react-redux": "^4.4.5", + "redux": "^3.5.2", "redux-logger": "^2.4.0", "redux-thunk": "^1.0.3" }, "devDependencies": { + "babel-cli": "^6.10.1", "babel-core": "^6.3.15", "babel-loader": "^6.2.0", + "babel-plugin-transform-object-rest-spread": "^6.8.0", "babel-preset-es2015": "^6.3.13", "babel-preset-react": "^6.3.13", - "babel-preset-react-hmre": "^1.1.1", "expect": "^1.6.0", "express": "^4.13.3", "node-libs-browser": "^0.5.2", diff --git a/examples/async/actions/index.js b/examples/async/src/actions/index.js similarity index 74% rename from examples/async/actions/index.js rename to examples/async/src/actions/index.js index 6553788c63..185829539f 100644 --- a/examples/async/actions/index.js +++ b/examples/async/src/actions/index.js @@ -1,37 +1,32 @@ 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, + type: 'SELECT_REDDIT', reddit } } export function invalidateReddit(reddit) { return { - type: INVALIDATE_REDDIT, + type: 'INVALIDATE_REDDIT', reddit } } function requestPosts(reddit) { return { - type: REQUEST_POSTS, + type: 'REQUEST_POSTS', reddit } } function receivePosts(reddit, json) { return { - type: RECEIVE_POSTS, - reddit, + type: 'RECEIVE_POSTS', posts: json.data.children.map(child => child.data), - receivedAt: Date.now() + receivedAt: Date.now(), + reddit } } diff --git a/examples/async/containers/App.js b/examples/async/src/components/App.js similarity index 70% rename from examples/async/containers/App.js rename to examples/async/src/components/App.js index 6109878fca..7e98a9792c 100644 --- a/examples/async/containers/App.js +++ b/examples/async/src/components/App.js @@ -1,8 +1,8 @@ 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' +import * as actions from '../actions' +import Picker from './Picker' +import Posts from './Posts' class App extends Component { constructor(props) { @@ -12,27 +12,30 @@ class App extends Component { } componentDidMount() { - const { dispatch, selectedReddit } = this.props - dispatch(fetchPostsIfNeeded(selectedReddit)) + this.maybeFetchPosts() } - componentWillReceiveProps(nextProps) { - if (nextProps.selectedReddit !== this.props.selectedReddit) { - const { dispatch, selectedReddit } = nextProps - dispatch(fetchPostsIfNeeded(selectedReddit)) + componentDidUpdate(prevProps) { + if (this.props.selectedReddit !== prevProps.selectedReddit) { + this.maybeFetchPosts() } } + maybeFetchPosts(invalidateCache = false) { + const { selectedReddit } = this.props + if (invalidateCache) { + this.props.invalidateReddit(selectedReddit) + } + this.props.fetchPostsIfNeeded(selectedReddit) + } + handleChange(nextReddit) { - this.props.dispatch(selectReddit(nextReddit)) + this.props.selectReddit(nextReddit) } handleRefreshClick(e) { e.preventDefault() - - const { dispatch, selectedReddit } = this.props - dispatch(invalidateReddit(selectedReddit)) - dispatch(fetchPostsIfNeeded(selectedReddit)) + this.maybeFetchPosts(true) } render() { @@ -73,7 +76,9 @@ App.propTypes = { posts: PropTypes.array.isRequired, isFetching: PropTypes.bool.isRequired, lastUpdated: PropTypes.number, - dispatch: PropTypes.func.isRequired + selectReddit: PropTypes.func.isRequired, + invalidateReddit: PropTypes.func.isRequired, + fetchPostsIfNeeded: PropTypes.func.isRequired } function mapStateToProps(state) { @@ -95,4 +100,4 @@ function mapStateToProps(state) { } } -export default connect(mapStateToProps)(App) +export default connect(mapStateToProps, actions)(App) diff --git a/examples/async/src/components/Picker.js b/examples/async/src/components/Picker.js new file mode 100644 index 0000000000..4b3572e148 --- /dev/null +++ b/examples/async/src/components/Picker.js @@ -0,0 +1,25 @@ +import React, { PropTypes } from 'react' + +export default function Picker({ value, onChange, options }) { + return ( + +

{value}

+ +
+ ) +} + +Picker.propTypes = { + options: PropTypes.arrayOf( + PropTypes.string.isRequired + ).isRequired, + value: PropTypes.string.isRequired, + onChange: PropTypes.func.isRequired +} diff --git a/examples/async/src/components/Posts.js b/examples/async/src/components/Posts.js new file mode 100644 index 0000000000..3896894f37 --- /dev/null +++ b/examples/async/src/components/Posts.js @@ -0,0 +1,15 @@ +import React, { PropTypes } from 'react' + +export default function Posts({ posts }) { + return ( +
    + {posts.map((post, i) => +
  • {post.title}
  • + )} +
+ ) +} + +Posts.propTypes = { + posts: PropTypes.array.isRequired +} diff --git a/examples/async/src/components/Root.js b/examples/async/src/components/Root.js new file mode 100644 index 0000000000..c1646422a0 --- /dev/null +++ b/examples/async/src/components/Root.js @@ -0,0 +1,15 @@ +import React, { PropTypes } from 'react'; +import { Provider } from 'react-redux' +import App from './App' + +export default function Root({ store }) { + return ( + + + + ) +} + +Root.propTypes = { + store: PropTypes.object.isRequired +} diff --git a/examples/async/index.js b/examples/async/src/index.js similarity index 62% rename from examples/async/index.js rename to examples/async/src/index.js index 12bcb25c37..5c36e717cc 100644 --- a/examples/async/index.js +++ b/examples/async/src/index.js @@ -1,15 +1,12 @@ import 'babel-polyfill' import React from 'react' import { render } from 'react-dom' -import { Provider } from 'react-redux' -import App from './containers/App' +import Root from './components/Root' import configureStore from './store/configureStore' const store = configureStore() render( - - - , + , document.getElementById('root') ) diff --git a/examples/async/reducers/index.js b/examples/async/src/reducers/index.js similarity index 58% rename from examples/async/reducers/index.js rename to examples/async/src/reducers/index.js index f936cbced3..d31c805184 100644 --- a/examples/async/reducers/index.js +++ b/examples/async/src/reducers/index.js @@ -1,12 +1,8 @@ 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: + case 'SELECT_REDDIT': return action.reddit default: return state @@ -19,22 +15,25 @@ function posts(state = { items: [] }, action) { switch (action.type) { - case INVALIDATE_REDDIT: - return Object.assign({}, state, { + case 'INVALIDATE_REDDIT': + return { + ...state, didInvalidate: true - }) - case REQUEST_POSTS: - return Object.assign({}, state, { + } + case 'REQUEST_POSTS': + return { + ...state, isFetching: true, didInvalidate: false - }) - case RECEIVE_POSTS: - return Object.assign({}, state, { + } + case 'RECEIVE_POSTS': + return { + ...state, isFetching: false, didInvalidate: false, items: action.posts, lastUpdated: action.receivedAt - }) + } default: return state } @@ -42,20 +41,21 @@ function posts(state = { function postsByReddit(state = { }, action) { switch (action.type) { - case INVALIDATE_REDDIT: - case RECEIVE_POSTS: - case REQUEST_POSTS: - return Object.assign({}, state, { + case 'INVALIDATE_REDDIT': + case 'RECEIVE_POSTS': + case 'REQUEST_POSTS': + return { + ...state, [action.reddit]: posts(state[action.reddit], action) - }) + } default: return state } } -const rootReducer = combineReducers({ +const reducer = combineReducers({ postsByReddit, selectedReddit }) -export default rootReducer +export default reducer diff --git a/examples/async/store/configureStore.js b/examples/async/src/store/configureStore.js similarity index 62% rename from examples/async/store/configureStore.js rename to examples/async/src/store/configureStore.js index c8964c2dca..e50f5fcebb 100644 --- a/examples/async/store/configureStore.js +++ b/examples/async/src/store/configureStore.js @@ -1,13 +1,12 @@ import { createStore, applyMiddleware } from 'redux' -import thunkMiddleware from 'redux-thunk' +import thunk from 'redux-thunk' import createLogger from 'redux-logger' -import rootReducer from '../reducers' +import reducer from '../reducers' -export default function configureStore(preloadedState) { +export default function configureStore() { const store = createStore( - rootReducer, - preloadedState, - applyMiddleware(thunkMiddleware, createLogger()) + reducer, + applyMiddleware(thunk, createLogger()) ) if (module.hot) { diff --git a/examples/async/webpack.config.js b/examples/async/webpack.dev.config.js similarity index 88% rename from examples/async/webpack.config.js rename to examples/async/webpack.dev.config.js index 2a6d9582c4..67087448cb 100644 --- a/examples/async/webpack.config.js +++ b/examples/async/webpack.dev.config.js @@ -5,7 +5,7 @@ module.exports = { devtool: 'cheap-module-eval-source-map', entry: [ 'webpack-hot-middleware/client', - './index' + './src/index' ], output: { path: path.join(__dirname, 'dist'), @@ -13,7 +13,6 @@ module.exports = { publicPath: '/static/' }, plugins: [ - new webpack.optimize.OccurrenceOrderPlugin(), new webpack.HotModuleReplacementPlugin() ], module: { diff --git a/examples/async/webpack.prod.config.js b/examples/async/webpack.prod.config.js new file mode 100644 index 0000000000..67087448cb --- /dev/null +++ b/examples/async/webpack.prod.config.js @@ -0,0 +1,28 @@ +var path = require('path') +var webpack = require('webpack') + +module.exports = { + devtool: 'cheap-module-eval-source-map', + entry: [ + 'webpack-hot-middleware/client', + './src/index' + ], + output: { + path: path.join(__dirname, 'dist'), + filename: 'bundle.js', + publicPath: '/static/' + }, + plugins: [ + new webpack.HotModuleReplacementPlugin() + ], + module: { + loaders: [ + { + test: /\.js$/, + loaders: ['babel'], + exclude: /node_modules/, + include: __dirname + } + ] + } +} From 73875040d1104a0ca4802de358199473706f732f Mon Sep 17 00:00:00 2001 From: Dan Abramov Date: Sun, 12 Jun 2016 02:25:43 +0100 Subject: [PATCH 43/44] Modernize async example setup --- examples/async/.babelrc | 2 +- examples/async/devServer.js | 23 ------------------ examples/async/index.html | 11 --------- examples/async/package.json | 13 +++++----- examples/async/server.dev.js | 23 ++++++++++++++++++ examples/async/src/index.js | 13 ++++++---- examples/async/src/store/configureStore.js | 3 +-- examples/async/webpack.config.dev.js | 20 ++++++++++++++++ examples/async/webpack.config.prod.js | 25 +++++++++++++++++++ examples/async/webpack.dev.config.js | 28 ---------------------- examples/async/webpack.prod.config.js | 28 ---------------------- 11 files changed, 86 insertions(+), 103 deletions(-) delete mode 100644 examples/async/devServer.js delete mode 100644 examples/async/index.html create mode 100644 examples/async/server.dev.js create mode 100644 examples/async/webpack.config.dev.js create mode 100644 examples/async/webpack.config.prod.js delete mode 100644 examples/async/webpack.dev.config.js delete mode 100644 examples/async/webpack.prod.config.js diff --git a/examples/async/.babelrc b/examples/async/.babelrc index 47c9aceb7e..064a80f366 100644 --- a/examples/async/.babelrc +++ b/examples/async/.babelrc @@ -1,4 +1,4 @@ { - "presets": ["es2015", "react"], + "presets": ["es2015-webpack", "react"], "plugins": ["transform-object-rest-spread"] } diff --git a/examples/async/devServer.js b/examples/async/devServer.js deleted file mode 100644 index d655597867..0000000000 --- a/examples/async/devServer.js +++ /dev/null @@ -1,23 +0,0 @@ -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/index.html b/examples/async/index.html deleted file mode 100644 index b89496eae0..0000000000 --- a/examples/async/index.html +++ /dev/null @@ -1,11 +0,0 @@ - - - - Redux async example - - -
-
- - - diff --git a/examples/async/package.json b/examples/async/package.json index 4bb279555c..f2742b824f 100644 --- a/examples/async/package.json +++ b/examples/async/package.json @@ -2,7 +2,8 @@ "name": "redux-async-example", "description": "Redux async example", "scripts": { - "start": "node devServer.js" + "start": "node server.dev.js", + "build": "webpack --config webpack.config.prod.js" }, "repository": { "type": "git", @@ -21,17 +22,17 @@ "redux-thunk": "^1.0.3" }, "devDependencies": { - "babel-cli": "^6.10.1", "babel-core": "^6.3.15", "babel-loader": "^6.2.0", "babel-plugin-transform-object-rest-spread": "^6.8.0", - "babel-preset-es2015": "^6.3.13", + "babel-preset-es2015-webpack": "^6.4.1", "babel-preset-react": "^6.3.13", "expect": "^1.6.0", "express": "^4.13.3", + "html-webpack-plugin": "^2.20.0", "node-libs-browser": "^0.5.2", - "webpack": "^1.9.11", - "webpack-dev-middleware": "^1.2.0", - "webpack-hot-middleware": "^2.9.1" + "webpack": "^2.1.0-beta.13", + "webpack-dev-middleware": "^1.6.1", + "webpack-hot-middleware": "^2.10.0" } } diff --git a/examples/async/server.dev.js b/examples/async/server.dev.js new file mode 100644 index 0000000000..0c6d3a166a --- /dev/null +++ b/examples/async/server.dev.js @@ -0,0 +1,23 @@ +const webpack = require('webpack') +const express = require('express') +const webpackDevMiddleware = require('webpack-dev-middleware') +const webpackHotMiddleware = require('webpack-hot-middleware') +const config = require('./webpack.config.dev') + +const app = express() +const port = 3000 +const compiler = webpack(config) + +app.use(webpackDevMiddleware(compiler, { + noInfo: true, + publicPath: config.output.publicPath +})) +app.use(webpackHotMiddleware(compiler)) + +app.listen(port, (error) => { + if (error) { + console.error(error) + } else { + console.info('🌎 Open http://localhost:%s/ in a web browser', port) + } +}) diff --git a/examples/async/src/index.js b/examples/async/src/index.js index 5c36e717cc..b3ac112969 100644 --- a/examples/async/src/index.js +++ b/examples/async/src/index.js @@ -4,9 +4,14 @@ import { render } from 'react-dom' import Root from './components/Root' import configureStore from './store/configureStore' +const rootEl = document.createElement('div') +document.body.appendChild(rootEl) + const store = configureStore() +render(, rootEl) -render( - , - document.getElementById('root') -) +if (module.hot) { + module.hot.accept('./components/Root', () => { + render(, rootEl) + }) +} diff --git a/examples/async/src/store/configureStore.js b/examples/async/src/store/configureStore.js index e50f5fcebb..f28e73ce5b 100644 --- a/examples/async/src/store/configureStore.js +++ b/examples/async/src/store/configureStore.js @@ -12,8 +12,7 @@ export default function configureStore() { if (module.hot) { // Enable Webpack hot module replacement for reducers module.hot.accept('../reducers', () => { - const nextRootReducer = require('../reducers').default - store.replaceReducer(nextRootReducer) + store.replaceReducer(reducer) }) } diff --git a/examples/async/webpack.config.dev.js b/examples/async/webpack.config.dev.js new file mode 100644 index 0000000000..475e66497a --- /dev/null +++ b/examples/async/webpack.config.dev.js @@ -0,0 +1,20 @@ +var path = require('path') +var webpack = require('webpack') +var HtmlWebpackPlugin = require('html-webpack-plugin'); + +module.exports = { + devtool: 'cheap-module-eval-source-map', + entry: ['./src/index', 'webpack-hot-middleware/client'], + plugins: [ + new webpack.HotModuleReplacementPlugin(), + new HtmlWebpackPlugin({ title: 'Redux async example' }), + new webpack.DefinePlugin({ 'process.env.NODE_ENV': '"development"' }) + ], + module: { + loaders: [{ + test: /\.js$/, + loaders: ['babel'], + include: path.join(__dirname, 'src') + }] + } +} diff --git a/examples/async/webpack.config.prod.js b/examples/async/webpack.config.prod.js new file mode 100644 index 0000000000..3141e9c04c --- /dev/null +++ b/examples/async/webpack.config.prod.js @@ -0,0 +1,25 @@ +var path = require('path') +var webpack = require('webpack') +var HtmlWebpackPlugin = require('html-webpack-plugin'); + +module.exports = { + entry: './src/index', + output: { + path: path.join(__dirname, 'dist'), + filename: '[name].[hash].js', + publicPath: '/' + }, + plugins: [ + new HtmlWebpackPlugin({ title: 'Redux async example' }), + new webpack.DefinePlugin({ 'process.env.NODE_ENV': '"production"' }), + new webpack.optimize.OccurrenceOrderPlugin(), + new webpack.optimize.UglifyJsPlugin() + ], + module: { + loaders: [{ + test: /\.js$/, + loaders: ['babel'], + include: path.join(__dirname, 'src') + }] + } +} diff --git a/examples/async/webpack.dev.config.js b/examples/async/webpack.dev.config.js deleted file mode 100644 index 67087448cb..0000000000 --- a/examples/async/webpack.dev.config.js +++ /dev/null @@ -1,28 +0,0 @@ -var path = require('path') -var webpack = require('webpack') - -module.exports = { - devtool: 'cheap-module-eval-source-map', - entry: [ - 'webpack-hot-middleware/client', - './src/index' - ], - output: { - path: path.join(__dirname, 'dist'), - filename: 'bundle.js', - publicPath: '/static/' - }, - plugins: [ - new webpack.HotModuleReplacementPlugin() - ], - module: { - loaders: [ - { - test: /\.js$/, - loaders: ['babel'], - exclude: /node_modules/, - include: __dirname - } - ] - } -} diff --git a/examples/async/webpack.prod.config.js b/examples/async/webpack.prod.config.js deleted file mode 100644 index 67087448cb..0000000000 --- a/examples/async/webpack.prod.config.js +++ /dev/null @@ -1,28 +0,0 @@ -var path = require('path') -var webpack = require('webpack') - -module.exports = { - devtool: 'cheap-module-eval-source-map', - entry: [ - 'webpack-hot-middleware/client', - './src/index' - ], - output: { - path: path.join(__dirname, 'dist'), - filename: 'bundle.js', - publicPath: '/static/' - }, - plugins: [ - new webpack.HotModuleReplacementPlugin() - ], - module: { - loaders: [ - { - test: /\.js$/, - loaders: ['babel'], - exclude: /node_modules/, - include: __dirname - } - ] - } -} From f7b522086cd3e8e95cb03a401dcec5fd8018b88f Mon Sep 17 00:00:00 2001 From: Max Date: Tue, 14 Jun 2016 18:25:11 +0200 Subject: [PATCH 44/44] Modernize counter example setup --- examples/counter/.babelrc | 8 ++--- examples/counter/index.html | 11 ------- examples/counter/package.json | 14 +++++---- examples/counter/server.dev.js | 23 +++++++++++++++ examples/counter/server.js | 23 --------------- .../counter/{ => src}/components/Counter.js | 0 examples/counter/{ => src}/index.js | 12 ++++++-- examples/counter/{ => src}/reducers/index.js | 0 examples/counter/{ => src}/test/.eslintrc | 0 .../{ => src}/test/components/Counter.spec.js | 0 .../{ => src}/test/reducers/counter.spec.js | 0 examples/counter/webpack.config.dev.js | 20 +++++++++++++ examples/counter/webpack.config.js | 29 ------------------- examples/counter/webpack.config.prod.js | 25 ++++++++++++++++ 14 files changed, 88 insertions(+), 77 deletions(-) delete mode 100644 examples/counter/index.html create mode 100644 examples/counter/server.dev.js delete mode 100644 examples/counter/server.js rename examples/counter/{ => src}/components/Counter.js (100%) rename examples/counter/{ => src}/index.js (68%) rename examples/counter/{ => src}/reducers/index.js (100%) rename examples/counter/{ => src}/test/.eslintrc (100%) rename examples/counter/{ => src}/test/components/Counter.spec.js (100%) rename examples/counter/{ => src}/test/reducers/counter.spec.js (100%) create mode 100644 examples/counter/webpack.config.dev.js delete mode 100644 examples/counter/webpack.config.js create mode 100644 examples/counter/webpack.config.prod.js diff --git a/examples/counter/.babelrc b/examples/counter/.babelrc index d0962f5695..064a80f366 100644 --- a/examples/counter/.babelrc +++ b/examples/counter/.babelrc @@ -1,8 +1,4 @@ { - "presets": ["es2015", "react"], - "env": { - "development": { - "presets": ["react-hmre"] - } - } + "presets": ["es2015-webpack", "react"], + "plugins": ["transform-object-rest-spread"] } diff --git a/examples/counter/index.html b/examples/counter/index.html deleted file mode 100644 index 1eff16a567..0000000000 --- a/examples/counter/index.html +++ /dev/null @@ -1,11 +0,0 @@ - - - - Redux counter example - - -
-
- - - diff --git a/examples/counter/package.json b/examples/counter/package.json index bb69e653c9..7ec41f4a30 100644 --- a/examples/counter/package.json +++ b/examples/counter/package.json @@ -3,7 +3,8 @@ "version": "0.0.0", "description": "Redux counter example", "scripts": { - "start": "node server.js", + "start": "node server.dev.js", + "build": "webpack --config webpack.config.prod.js", "test": "cross-env NODE_ENV=test mocha --recursive --compilers js:babel-register", "test:watch": "npm test -- --watch" }, @@ -25,19 +26,20 @@ "devDependencies": { "babel-core": "^6.3.15", "babel-loader": "^6.2.0", - "babel-preset-es2015": "^6.3.13", + "babel-plugin-transform-object-rest-spread": "^6.8.0", + "babel-preset-es2015-webpack": "^6.4.1", "babel-preset-react": "^6.3.13", - "babel-preset-react-hmre": "^1.1.1", "babel-register": "^6.3.13", "cross-env": "^1.0.7", "enzyme": "^2.0.0", "expect": "^1.6.0", "express": "^4.13.3", + "html-webpack-plugin": "^2.21.0", "mocha": "^2.2.5", "node-libs-browser": "^0.5.2", "react-addons-test-utils": "^0.14.7", - "webpack": "^1.9.11", - "webpack-dev-middleware": "^1.2.0", - "webpack-hot-middleware": "^2.9.1" + "webpack": "^2.1.0-beta.13", + "webpack-dev-middleware": "^1.6.1", + "webpack-hot-middleware": "^2.10.0" } } diff --git a/examples/counter/server.dev.js b/examples/counter/server.dev.js new file mode 100644 index 0000000000..0c6d3a166a --- /dev/null +++ b/examples/counter/server.dev.js @@ -0,0 +1,23 @@ +const webpack = require('webpack') +const express = require('express') +const webpackDevMiddleware = require('webpack-dev-middleware') +const webpackHotMiddleware = require('webpack-hot-middleware') +const config = require('./webpack.config.dev') + +const app = express() +const port = 3000 +const compiler = webpack(config) + +app.use(webpackDevMiddleware(compiler, { + noInfo: true, + publicPath: config.output.publicPath +})) +app.use(webpackHotMiddleware(compiler)) + +app.listen(port, (error) => { + if (error) { + console.error(error) + } else { + console.info('🌎 Open http://localhost:%s/ in a web browser', port) + } +}) diff --git a/examples/counter/server.js b/examples/counter/server.js deleted file mode 100644 index d655597867..0000000000 --- a/examples/counter/server.js +++ /dev/null @@ -1,23 +0,0 @@ -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/counter/components/Counter.js b/examples/counter/src/components/Counter.js similarity index 100% rename from examples/counter/components/Counter.js rename to examples/counter/src/components/Counter.js diff --git a/examples/counter/index.js b/examples/counter/src/index.js similarity index 68% rename from examples/counter/index.js rename to examples/counter/src/index.js index 60cac9cf27..a0b9319f6c 100644 --- a/examples/counter/index.js +++ b/examples/counter/src/index.js @@ -4,8 +4,10 @@ import { createStore } from 'redux' import Counter from './components/Counter' import counter from './reducers' +const rootEl = document.createElement('div') +document.body.appendChild(rootEl) + const store = createStore(counter) -const rootEl = document.getElementById('root') function render() { ReactDOM.render( @@ -14,9 +16,15 @@ function render() { onIncrement={() => store.dispatch({ type: 'INCREMENT' })} onDecrement={() => store.dispatch({ type: 'DECREMENT' })} />, - rootEl + rootEl ) } render() store.subscribe(render) + +if (module.hot) { + module.hot.accept('./components/Counter', () => { + render(, rootEl) + }) +} diff --git a/examples/counter/reducers/index.js b/examples/counter/src/reducers/index.js similarity index 100% rename from examples/counter/reducers/index.js rename to examples/counter/src/reducers/index.js diff --git a/examples/counter/test/.eslintrc b/examples/counter/src/test/.eslintrc similarity index 100% rename from examples/counter/test/.eslintrc rename to examples/counter/src/test/.eslintrc diff --git a/examples/counter/test/components/Counter.spec.js b/examples/counter/src/test/components/Counter.spec.js similarity index 100% rename from examples/counter/test/components/Counter.spec.js rename to examples/counter/src/test/components/Counter.spec.js diff --git a/examples/counter/test/reducers/counter.spec.js b/examples/counter/src/test/reducers/counter.spec.js similarity index 100% rename from examples/counter/test/reducers/counter.spec.js rename to examples/counter/src/test/reducers/counter.spec.js diff --git a/examples/counter/webpack.config.dev.js b/examples/counter/webpack.config.dev.js new file mode 100644 index 0000000000..2b2991a2b2 --- /dev/null +++ b/examples/counter/webpack.config.dev.js @@ -0,0 +1,20 @@ +var path = require('path') +var webpack = require('webpack') +var HtmlWebpackPlugin = require('html-webpack-plugin') + +module.exports = { + devtool: 'cheap-module-eval-source-map', + entry: [ './src/index', 'webpack-hot-middleware/client' ], + plugins: [ + new webpack.HotModuleReplacementPlugin(), + new HtmlWebpackPlugin({ title: 'Redux counter example' }), + new webpack.DefinePlugin({ 'process.env.NODE_ENV': '"development"' }) + ], + module: { + loaders: [ { + test: /\.js$/, + loaders: [ 'babel' ], + include: path.join(__dirname, 'src') + } ] + } +} diff --git a/examples/counter/webpack.config.js b/examples/counter/webpack.config.js deleted file mode 100644 index 1c7f5f2dc1..0000000000 --- a/examples/counter/webpack.config.js +++ /dev/null @@ -1,29 +0,0 @@ -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.OccurrenceOrderPlugin(), - new webpack.HotModuleReplacementPlugin() - ], - module: { - loaders: [ - { - test: /\.js$/, - loaders: [ 'babel' ], - exclude: /node_modules/, - include: __dirname - } - ] - } -} diff --git a/examples/counter/webpack.config.prod.js b/examples/counter/webpack.config.prod.js new file mode 100644 index 0000000000..01314b78b7 --- /dev/null +++ b/examples/counter/webpack.config.prod.js @@ -0,0 +1,25 @@ +var path = require('path') +var webpack = require('webpack') +var HtmlWebpackPlugin = require('html-webpack-plugin') + +module.exports = { + entry: './src/index', + output: { + path: path.join(__dirname, 'dist'), + filename: '[name].[hash].js', + publicPath: '/' + }, + plugins: [ + new HtmlWebpackPlugin({ title: 'Redux counter example' }), + new webpack.DefinePlugin({ 'process.env.NODE_ENV': '"production"' }), + new webpack.optimize.OccurrenceOrderPlugin(), + new webpack.optimize.UglifyJsPlugin() + ], + module: { + loaders: [ { + test: /\.js$/, + loaders: [ 'babel' ], + include: path.join(__dirname, 'src') + } ] + } +}