diff --git a/examples/redux/.babelrc b/examples/redux/.babelrc new file mode 100644 index 0000000..4a129cb --- /dev/null +++ b/examples/redux/.babelrc @@ -0,0 +1,10 @@ +{ + "presets": [ + "es2015", + "stage-1", + "react" + ], + "plugins": [ + "transform-decorators-legacy" + ] +} diff --git a/examples/redux/client.js b/examples/redux/client.js new file mode 100644 index 0000000..c38b5ff --- /dev/null +++ b/examples/redux/client.js @@ -0,0 +1,6 @@ +import render from './render/client'; +import routes from './routes'; +import configureStore from './redux/configureStore'; + +const store = configureStore(__FLUX_STATE__); +render(document.getElementById('application'), routes, store); diff --git a/examples/redux/components/App.js b/examples/redux/components/App.js new file mode 100644 index 0000000..1dcec21 --- /dev/null +++ b/examples/redux/components/App.js @@ -0,0 +1,27 @@ +import React, { Component } from 'react'; +import { Link, IndexLink } from 'react-router'; + +export default class App extends Component { + render() { + const { loading } = this.props + const style = { + opacity: loading ? 0.5 : 1, + transition: loading ? 'opacity 250ms ease 300ms' : 'false' + } + + return ( +
+

React Router Redial Example

+ + {this.props.children} +
+ ); + } +} diff --git a/examples/redux/components/Github.js b/examples/redux/components/Github.js new file mode 100644 index 0000000..5b23d72 --- /dev/null +++ b/examples/redux/components/Github.js @@ -0,0 +1,32 @@ +import React, { Component } from 'react'; +import { Link } from 'react-router'; + +export default class Github extends Component { + + constructor(props) { + super(props); + this.state = {value: ''}; + } + + onChange = (e) => { + this.setState({value: e.target.value}); + } + + render() { + const children = this.props.children ? + this.props.children : + ( +
+

Please select a user by typing and clicking the link

+   + Open user! +
+ ); + return ( +
+

Github

+ { children } +
+ ); + } +} diff --git a/examples/redux/components/Index.js b/examples/redux/components/Index.js new file mode 100644 index 0000000..e723bf6 --- /dev/null +++ b/examples/redux/components/Index.js @@ -0,0 +1,36 @@ +import React, { Component } from 'react'; +import { provideHooks } from 'redial'; + +@provideHooks({ + fetch: ({ setProps, getProps, force }) => new Promise((resolve) => { + const { color } = getProps(); + if(!color || force) { + setTimeout(() => { + const getValue = () => Math.round(Math.random() * 255); + setProps({color: `rgb(${getValue()}, ${getValue()}, ${getValue()})`}); + resolve(); + }, 1000); + } else { + resolve(); + } + }), + defer: ({ setProps, getProps, force }) => { + const { data } = getProps(); + if(!data || force) { + // Will be available as this.props.data on the component + setProps({ data: 'Client data' }) + } + } +}) +export default class Index extends Component { + render() { + return ( +
+

React Router Redial

+

{ this.props.data }

+ +
{ JSON.stringify(this.props, null, 2) }
+
+ ); + } +} diff --git a/examples/redux/components/User.js b/examples/redux/components/User.js new file mode 100644 index 0000000..ce72560 --- /dev/null +++ b/examples/redux/components/User.js @@ -0,0 +1,28 @@ +import React, { Component } from 'react'; +import { provideHooks } from 'redial'; +import { connect } from 'react-redux'; + +import fetchGithubUser from '../redux/actions/fetchGithubUser'; + +function mapStateToProps(state, ownProps) { + return { user: state.githubUsers[ownProps.params.id] } +} + +@provideHooks({ + fetch: ({ params: { id }, dispatch, getState }) => { + if(!getState().githubUsers[id]) { + return dispatch(fetchGithubUser(id)); + } + } +}) +@connect(mapStateToProps) +export default class Index extends Component { + render() { + return ( +
+

{ this.props.user.name }
@{ this.props.user.login }

+ +
+ ); + } +} diff --git a/examples/redux/devServer.js b/examples/redux/devServer.js new file mode 100644 index 0000000..7e0c6ce --- /dev/null +++ b/examples/redux/devServer.js @@ -0,0 +1,26 @@ +import koa from 'koa'; +import webpack from 'webpack'; +import koaWebpackDevMiddleware from 'koa-webpack-dev-middleware'; + +import webpackConfig from './webpack.config.js'; + +const devServer = koa(); +const compiler = webpack(webpackConfig); + +devServer.use( + koaWebpackDevMiddleware(compiler, { + publicPath: '/', + noInfo: false, + quiet: false + }) +); + +const hotMiddleware = require('webpack-hot-middleware')(compiler); +devServer.use(function* (next) { + yield hotMiddleware.bind(null, this.req, this.res); + yield next; +}); + +devServer.listen(3001); + +console.log('Dev server started on http://localhost:3001'); diff --git a/examples/redux/package.json b/examples/redux/package.json new file mode 100644 index 0000000..fc18217 --- /dev/null +++ b/examples/redux/package.json @@ -0,0 +1,36 @@ +{ + "name": "react-router-redial-redux-example", + "version": "0.0.0", + "description": "React Router Redial Redux example", + "scripts": { + "start": "babel-node server.js" + }, + "license": "MIT", + "dependencies": { + "isomorphic-fetch": "2.2.1", + "koa": "1.2.0", + "nunjucks": "2.4.2", + "react": "15.0.2", + "react-dom": "15.0.2", + "redux-devtools": "3.2.0", + "redux-devtools-dock-monitor": "1.1.1", + "redux-devtools-log-monitor": "1.0.11", + "redux-logger": "2.6.1", + "redux-thunk": "2.1.0" + }, + "devDependencies": { + "babel-cli": "6.8.0", + "babel-core": "6.8.0", + "babel-loader": "6.2.4", + "babel-plugin-transform-decorators-legacy": "1.3.4", + "babel-preset-es2015": "6.6.0", + "babel-preset-react": "6.5.0", + "babel-preset-stage-1": "6.5.0", + "koa-webpack-dev-middleware": "1.2.0", + "react-router": "2.4.0", + "react-router-redial": "0.1.3", + "redial": "0.4.1", + "webpack": "1.13.0", + "webpack-hot-middleware": "2.10.0" + } +} diff --git a/examples/redux/redux/actions/fetchGithubUser.js b/examples/redux/redux/actions/fetchGithubUser.js new file mode 100644 index 0000000..af48df0 --- /dev/null +++ b/examples/redux/redux/actions/fetchGithubUser.js @@ -0,0 +1,12 @@ +import fetch from 'isomorphic-fetch'; + +export default function fetchGithubUser(userId) { + return (dispatch) => { + return fetch(`https://api.github.com/users/${userId}`) + .then((response) => response.json()) + .then((user) => dispatch({ + type: 'USER_LOADED', + payload: user + })) + } +} diff --git a/examples/redux/redux/configureStore.js b/examples/redux/redux/configureStore.js new file mode 100644 index 0000000..3107cd1 --- /dev/null +++ b/examples/redux/redux/configureStore.js @@ -0,0 +1,21 @@ +import { createStore, applyMiddleware, compose, combineReducers } from 'redux' +import thunk from 'redux-thunk' +import createLogger from 'redux-logger' + +export default function configureStore(initialState) { + const store = createStore( + combineReducers(require('./reducers')), + initialState, + applyMiddleware(thunk, createLogger()), + ) + + if (module.hot) { + // Enable Webpack hot module replacement for reducers + module.hot.accept('./reducers', () => { + const nextRootReducer = combineReducers(require('./reducers')) + store.replaceReducer(nextRootReducer) + }) + } + + return store +} diff --git a/examples/redux/redux/reducers/index.js b/examples/redux/redux/reducers/index.js new file mode 100644 index 0000000..9e9829e --- /dev/null +++ b/examples/redux/redux/reducers/index.js @@ -0,0 +1,11 @@ +export function githubUsers(state = {}, action) { + switch (action.type) { + case 'USER_LOADED': + return { + ...state, + [action.payload.login]: action.payload + }; + default: + return state; + } +} diff --git a/examples/redux/render/client.js b/examples/redux/render/client.js new file mode 100644 index 0000000..951eb9a --- /dev/null +++ b/examples/redux/render/client.js @@ -0,0 +1,43 @@ +import { RedialContext } from 'react-router-redial'; + +import React from 'react'; +import { render } from 'react-dom'; +import { Router, browserHistory } from 'react-router'; +import { Provider } from 'react-redux'; + +// Render the app client-side to a given container element: +export default (container, routes, store) => { + // Define extra locals to be provided to all lifecycle hooks: + const locals = store ? { + dispatch: store.dispatch, + getState: store.getState, + } : {}; + + let component = ( + ( +
Loading…
} + /> + )} + /> + ); + + if (store) { + component = ( + + {component} + + ); + } + + // Render app to container element: + render(component, container); +}; diff --git a/examples/redux/render/server.js b/examples/redux/render/server.js new file mode 100644 index 0000000..5151d83 --- /dev/null +++ b/examples/redux/render/server.js @@ -0,0 +1,42 @@ +import { triggerHooks, RedialContext } from '../../../lib/index'; + +import React from 'react'; +import { renderToString } from 'react-dom/server'; +import { createMemoryHistory, match } from 'react-router'; +import { Provider } from 'react-redux'; + +// Render the app server-side for a given path: +export default (path, routes, store) => new Promise((resolve, reject) => { + // Set up history for router: + const history = createMemoryHistory(path); + + // Match routes based on history object: + match({ routes, history }, (error, redirectLocation, renderProps) => { + + // Define extra locals to be provided to all lifecycle hooks: + const locals = store ? { + dispatch: store.dispatch, + getState: store.getState + } : {}; + + // Wait for async data fetching to complete, then render: + triggerHooks({ + renderProps, + locals, + hooks: [ 'fetch', 'done' ] + }).then(({ redialMap, redialProps }) => { + const state = store ? store.getState() : null; + const component = ; + const html = store ? renderToString( + + { component } + + ) : renderToString(component); + + // Important that the redialProps are sent to the client + // by serializing it and setting it on window.__REDIAL_PROPS__ + resolve({ html, state, redialProps }); + }) + .catch(reject); + }); +}); diff --git a/examples/redux/routes.js b/examples/redux/routes.js new file mode 100644 index 0000000..886eab3 --- /dev/null +++ b/examples/redux/routes.js @@ -0,0 +1,16 @@ +import React from 'react'; +import { Router, Route, IndexRoute } from 'react-router' + +import App from './components/App'; +import Index from './components/Index'; +import Github from './components/Github'; +import User from './components/User'; + +export default ( + + + + + + +) diff --git a/examples/redux/server.js b/examples/redux/server.js new file mode 100644 index 0000000..70af7fc --- /dev/null +++ b/examples/redux/server.js @@ -0,0 +1,24 @@ +import koa from 'koa'; +import nunjucks from 'nunjucks'; + +import render from './render/server'; +import routes from './routes'; +import configureStore from './redux/configureStore'; + +require('./devServer'); + +nunjucks.configure(__dirname); + +const server = koa(); +server.use(function *() { + const store = configureStore(); + const result = yield render(this.url, routes, store); + this.body = nunjucks.render('template.html', { + ...result, + state: JSON.stringify(result.state), + redialProps: JSON.stringify(result.redialProps) + }); +}); + +server.listen(3000); +console.log('Server started on http://localhost:3000'); diff --git a/examples/redux/template.html b/examples/redux/template.html new file mode 100644 index 0000000..13f1e2f --- /dev/null +++ b/examples/redux/template.html @@ -0,0 +1,14 @@ + + + + + + +
{{ html | safe }}
+ + + + + + + diff --git a/examples/redux/webpack.config.js b/examples/redux/webpack.config.js new file mode 100644 index 0000000..e6d138c --- /dev/null +++ b/examples/redux/webpack.config.js @@ -0,0 +1,28 @@ +const path = require('path'); +const webpack = require('webpack'); + +module.exports = { + + devtool: 'source-map', + + entry: path.join(__dirname, 'client.js'), + + output: { + path: path.join(__dirname, 'build'), + filename: 'bundle.js', + publicPath: '/build/' + }, + + resolve: { + alias: { + 'react-router-redial': path.join(__dirname, '..', '..', 'src', 'index.js') + } + }, + + module: { + loaders: [ + { test: /\.js$/, exclude: /node_modules/, loader: require.resolve('babel-loader') } + ] + }, + +}; diff --git a/examples/simple/.babelrc b/examples/simple/.babelrc new file mode 100644 index 0000000..4a129cb --- /dev/null +++ b/examples/simple/.babelrc @@ -0,0 +1,10 @@ +{ + "presets": [ + "es2015", + "stage-1", + "react" + ], + "plugins": [ + "transform-decorators-legacy" + ] +} diff --git a/examples/simple/client.js b/examples/simple/client.js new file mode 100644 index 0000000..07e02e1 --- /dev/null +++ b/examples/simple/client.js @@ -0,0 +1,4 @@ +import render from './render/client'; +import routes from './routes'; + +render(document.getElementById('application'), routes); diff --git a/examples/simple/components/App.js b/examples/simple/components/App.js new file mode 100644 index 0000000..1dcec21 --- /dev/null +++ b/examples/simple/components/App.js @@ -0,0 +1,27 @@ +import React, { Component } from 'react'; +import { Link, IndexLink } from 'react-router'; + +export default class App extends Component { + render() { + const { loading } = this.props + const style = { + opacity: loading ? 0.5 : 1, + transition: loading ? 'opacity 250ms ease 300ms' : 'false' + } + + return ( +
+

React Router Redial Example

+
    +
  • + Start +
  • +
  • + Github +
  • +
+ {this.props.children} +
+ ); + } +} diff --git a/examples/simple/components/Github.js b/examples/simple/components/Github.js new file mode 100644 index 0000000..5b23d72 --- /dev/null +++ b/examples/simple/components/Github.js @@ -0,0 +1,32 @@ +import React, { Component } from 'react'; +import { Link } from 'react-router'; + +export default class Github extends Component { + + constructor(props) { + super(props); + this.state = {value: ''}; + } + + onChange = (e) => { + this.setState({value: e.target.value}); + } + + render() { + const children = this.props.children ? + this.props.children : + ( +
+

Please select a user by typing and clicking the link

+   + Open user! +
+ ); + return ( +
+

Github

+ { children } +
+ ); + } +} diff --git a/examples/simple/components/Index.js b/examples/simple/components/Index.js new file mode 100644 index 0000000..e723bf6 --- /dev/null +++ b/examples/simple/components/Index.js @@ -0,0 +1,36 @@ +import React, { Component } from 'react'; +import { provideHooks } from 'redial'; + +@provideHooks({ + fetch: ({ setProps, getProps, force }) => new Promise((resolve) => { + const { color } = getProps(); + if(!color || force) { + setTimeout(() => { + const getValue = () => Math.round(Math.random() * 255); + setProps({color: `rgb(${getValue()}, ${getValue()}, ${getValue()})`}); + resolve(); + }, 1000); + } else { + resolve(); + } + }), + defer: ({ setProps, getProps, force }) => { + const { data } = getProps(); + if(!data || force) { + // Will be available as this.props.data on the component + setProps({ data: 'Client data' }) + } + } +}) +export default class Index extends Component { + render() { + return ( +
+

React Router Redial

+

{ this.props.data }

+ +
{ JSON.stringify(this.props, null, 2) }
+
+ ); + } +} diff --git a/examples/simple/components/User.js b/examples/simple/components/User.js new file mode 100644 index 0000000..c74f572 --- /dev/null +++ b/examples/simple/components/User.js @@ -0,0 +1,23 @@ +import React, { Component } from 'react'; +import { provideHooks } from 'redial'; +import fetch from 'isomorphic-fetch'; + +@provideHooks({ + fetch: ({ params, setProps, getProps }) => { + if (!getProps().user || getProps().user.login !== params.id) { + return fetch(`https://api.github.com/users/${params.id}`) + .then((response) => response.json()) + .then((user) => setProps({ user })) + } + } +}) +export default class Index extends Component { + render() { + return ( +
+

{ this.props.user.name }
@{ this.props.user.login }

+ +
+ ); + } +} diff --git a/examples/simple/devServer.js b/examples/simple/devServer.js new file mode 100644 index 0000000..7e0c6ce --- /dev/null +++ b/examples/simple/devServer.js @@ -0,0 +1,26 @@ +import koa from 'koa'; +import webpack from 'webpack'; +import koaWebpackDevMiddleware from 'koa-webpack-dev-middleware'; + +import webpackConfig from './webpack.config.js'; + +const devServer = koa(); +const compiler = webpack(webpackConfig); + +devServer.use( + koaWebpackDevMiddleware(compiler, { + publicPath: '/', + noInfo: false, + quiet: false + }) +); + +const hotMiddleware = require('webpack-hot-middleware')(compiler); +devServer.use(function* (next) { + yield hotMiddleware.bind(null, this.req, this.res); + yield next; +}); + +devServer.listen(3001); + +console.log('Dev server started on http://localhost:3001'); diff --git a/examples/simple/package.json b/examples/simple/package.json new file mode 100644 index 0000000..76318c1 --- /dev/null +++ b/examples/simple/package.json @@ -0,0 +1,31 @@ +{ + "name": "react-router-redial-simple-example", + "version": "0.0.0", + "description": "React Router Redial simple example", + "scripts": { + "start": "babel-node server.js" + }, + "license": "MIT", + "dependencies": { + "isomorphic-fetch": "2.2.1", + "koa": "1.2.0", + "nunjucks": "2.4.2", + "react": "15.0.2", + "react-dom": "15.0.2", + "react-router": "2.4.0", + "react-router-redial": "0.1.3", + "redial": "0.4.1" + }, + "devDependencies": { + "babel-cli": "6.8.0", + "babel-core": "6.8.0", + "babel-loader": "6.2.4", + "babel-plugin-transform-decorators-legacy": "1.3.4", + "babel-preset-es2015": "6.6.0", + "babel-preset-react": "6.5.0", + "babel-preset-stage-1": "6.5.0", + "koa-webpack-dev-middleware": "1.2.0", + "webpack": "1.13.0", + "webpack-hot-middleware": "2.10.0" + } +} diff --git a/examples/simple/render/client.js b/examples/simple/render/client.js new file mode 100644 index 0000000..908049b --- /dev/null +++ b/examples/simple/render/client.js @@ -0,0 +1,27 @@ +import { RedialContext } from 'react-router-redial'; + +import React from 'react'; +import { render } from 'react-dom'; +import { Router, browserHistory } from 'react-router'; + +// Render the app client-side to a given container element: +export default (container, routes) => { + const component = ( + ( +
Loading…
} + /> + )} + /> + ); + + // Render app to container element: + render(component, container); +}; diff --git a/examples/simple/render/server.js b/examples/simple/render/server.js new file mode 100644 index 0000000..f4e9906 --- /dev/null +++ b/examples/simple/render/server.js @@ -0,0 +1,33 @@ +import { triggerHooks, RedialContext } from '../../../lib/index'; + +import React from 'react'; +import { renderToString } from 'react-dom/server'; +import { createMemoryHistory, match } from 'react-router'; + +// Render the app server-side for a given path: +export default (path, routes) => new Promise((resolve, reject) => { + // Set up history for router: + const history = createMemoryHistory(path); + + // Match routes based on history object: + match({ routes, history }, (error, redirectLocation, renderProps) => { + if (error || !renderProps) { + return resolve({ + html: '', + redialProps: null + }); + } + + // Wait for async data fetching to complete, then render: + triggerHooks({ + renderProps, + hooks: [ 'fetch', 'done' ] + }).then(({ redialMap, redialProps }) => { + const html = renderToString( + + ); + resolve({ html, redialProps }); + }) + .catch(reject); + }); +}); diff --git a/examples/simple/routes.js b/examples/simple/routes.js new file mode 100644 index 0000000..886eab3 --- /dev/null +++ b/examples/simple/routes.js @@ -0,0 +1,16 @@ +import React from 'react'; +import { Router, Route, IndexRoute } from 'react-router' + +import App from './components/App'; +import Index from './components/Index'; +import Github from './components/Github'; +import User from './components/User'; + +export default ( + + + + + + +) diff --git a/examples/simple/server.js b/examples/simple/server.js new file mode 100644 index 0000000..b93805d --- /dev/null +++ b/examples/simple/server.js @@ -0,0 +1,20 @@ +import koa from 'koa'; +import nunjucks from 'nunjucks'; + +import render from './render/server'; +import routes from './routes'; + +require('./devServer'); + +nunjucks.configure(__dirname); + +const server = koa(); +server.use(function *() { + const result = yield render(this.url, routes); + this.body = nunjucks.render('template.html', { + ...result, + redialProps: JSON.stringify(result.redialProps) + }); +}); +server.listen(3000); +console.log('Server started on http://localhost:3000'); diff --git a/examples/simple/template.html b/examples/simple/template.html new file mode 100644 index 0000000..721fa71 --- /dev/null +++ b/examples/simple/template.html @@ -0,0 +1,11 @@ + + + + + + +
{{ html | safe }}
+ + + + diff --git a/examples/simple/webpack.config.js b/examples/simple/webpack.config.js new file mode 100644 index 0000000..e6d138c --- /dev/null +++ b/examples/simple/webpack.config.js @@ -0,0 +1,28 @@ +const path = require('path'); +const webpack = require('webpack'); + +module.exports = { + + devtool: 'source-map', + + entry: path.join(__dirname, 'client.js'), + + output: { + path: path.join(__dirname, 'build'), + filename: 'bundle.js', + publicPath: '/build/' + }, + + resolve: { + alias: { + 'react-router-redial': path.join(__dirname, '..', '..', 'src', 'index.js') + } + }, + + module: { + loaders: [ + { test: /\.js$/, exclude: /node_modules/, loader: require.resolve('babel-loader') } + ] + }, + +};