Skip to content

Commit

Permalink
Upgrade to react 0.14.0-rc1 and react-router 1.0.0-rc1
Browse files Browse the repository at this point in the history
  • Loading branch information
stevoland committed Sep 25, 2015
1 parent fe03bce commit 5c7bb1d
Show file tree
Hide file tree
Showing 9 changed files with 97 additions and 91 deletions.
11 changes: 7 additions & 4 deletions package.json
Expand Up @@ -86,18 +86,20 @@
"express": "^4.13.0",
"express-session": "^1.11.3",
"file-loader": "^0.8.4",
"history": "^1.11.0",
"http-proxy": "^1.11.1",
"less-loader": "^2.2.1",
"lru-memoize": "1.0.0",
"map-props": "^1.0.0",
"piping": "0.2.0",
"pretty-error": "^1.1.2",
"query-string": "^2.4.0",
"react": "0.13.3",
"react-document-meta": "^1.0.0",
"react-inline-css": "1.2.1",
"react": "0.14.0-rc1",
"react-document-meta": "^2.0.0-rc2",
"react-dom": "0.14.0-rc1",
"react-inline-css": "2.0.0",
"react-redux": "^2.1.2",
"react-router": "v1.0.0-beta2",
"react-router": "1.0.0-rc1",
"redux": "^3.0.0",
"redux-form": "^1.6.0",
"serialize-javascript": "^1.0.0",
Expand Down Expand Up @@ -138,6 +140,7 @@
"mocha": "^2.2.5",
"node-sass": "^3.2.0",
"react-a11y": "0.2.6",
"react-addons-test-utils": "^0.14.0-rc1",
"react-hot-loader": "1.3.0",
"react-transform-hmr": "^1.0.0",
"redux-devtools": "^2.1.2",
Expand Down
51 changes: 29 additions & 22 deletions src/client.js
Expand Up @@ -3,22 +3,19 @@
*/
import 'babel/polyfill';
import React from 'react';
import BrowserHistory from 'react-router/lib/BrowserHistory';
import Location from 'react-router/lib/Location';
import queryString from 'query-string';
import ReactDOM from 'react-dom';
import createHistory from 'history/lib/createBrowserHistory';
import createLocation from 'history/lib/createLocation';
import createStore from './redux/create';
import ApiClient from './helpers/ApiClient';
import universalRouter from './helpers/universalRouter';
import io from 'socket.io-client';

const history = new BrowserHistory();
const history = createHistory();
const client = new ApiClient();

const dest = document.getElementById('content');
const store = createStore(client, window.__data);
const search = document.location.search;
const query = search && queryString.parse(search);
const location = new Location(document.location.pathname, query);

function initSocket() {
// TODO: add better support for production when running behind proxy (nginx)
Expand All @@ -36,22 +33,32 @@ function initSocket() {

window.socket = initSocket();

universalRouter(location, history, store)
.then(({component}) => {
React.render(component, dest);
if (__DEVTOOLS__) {
const { DevTools, DebugPanel, LogMonitor } = require('redux-devtools/lib/react');
React.render(<div>
{component}
<DebugPanel top right bottom key="debugPanel">
<DevTools store={store} monitor={LogMonitor}/>
</DebugPanel>
</div>, dest);
}
}, (error) => {
console.error(error);
});
const location = createLocation(document.location.pathname, document.location.search);

const render = (loc, hist, str, preload) => {
return universalRouter(loc, hist, str, preload)
.then(({component}) => {
ReactDOM.render(component, dest);
if (__DEVTOOLS__) {
const { DevTools, DebugPanel, LogMonitor } = require('redux-devtools/lib/react');
ReactDOM.render(<div>
{component}
<DebugPanel top right bottom key="debugPanel">
<DevTools store={store} monitor={LogMonitor}/>
</DebugPanel>
</div>, dest);
}
}, (error) => {
console.error(error);
});
};

history.listenBefore((loc, callback) => {
render(loc, history, store, true)
.then((callback));
});

render(location, history, store);

if (process.env.NODE_ENV !== 'production') {
window.React = React; // enable debugger
Expand Down
11 changes: 6 additions & 5 deletions src/components/__tests__/InfoBar-test.js
@@ -1,10 +1,11 @@
import React from 'react/addons';
import React from 'react';
import ReactDOM from 'react-dom';
import {renderIntoDocument} from 'react-addons-test-utils';
import { expect} from 'chai';
import { InfoBar } from 'components';
import { Provider } from 'react-redux';
import createStore from 'redux/create';
import ApiClient from 'helpers/ApiClient';
const { TestUtils } = React.addons;
const client = new ApiClient();

describe('InfoBar', () => {
Expand All @@ -21,12 +22,12 @@ describe('InfoBar', () => {
};

const store = createStore(client, mockStore);
const renderer = TestUtils.renderIntoDocument(
const renderer = renderIntoDocument(
<Provider store={store} key="provider">
{() => <InfoBar/>}
<InfoBar/>
</Provider>
);
const dom = React.findDOMNode(renderer);
const dom = ReactDOM.findDOMNode(renderer);

it('should render correctly', () => {
expect(renderer).to.be.ok;
Expand Down
20 changes: 4 additions & 16 deletions src/containers/App/App.js
Expand Up @@ -6,7 +6,6 @@ import DocumentMeta from 'react-document-meta';
import { isLoaded as isInfoLoaded, load as loadInfo } from 'redux/modules/info';
import { isLoaded as isAuthLoaded, load as loadAuth, logout } from 'redux/modules/auth';
import { InfoBar } from 'components';
import { createTransitionHook } from 'helpers/universalRouter';

const title = 'React Redux Example';
const description = 'All the modern best practices in one example.';
Expand Down Expand Up @@ -42,35 +41,24 @@ export default class App extends Component {
static propTypes = {
children: PropTypes.object.isRequired,
user: PropTypes.object,
logout: PropTypes.func.isRequired
logout: PropTypes.func.isRequired,
history: PropTypes.object
};

static contextTypes = {
router: PropTypes.object.isRequired,
store: PropTypes.object.isRequired
};

componentWillMount() {
const {router, store} = this.context;
this.transitionHook = createTransitionHook(store);
router.addTransitionHook(this.transitionHook);
}

componentWillReceiveProps(nextProps) {
if (!this.props.user && nextProps.user) {
// login
this.context.router.transitionTo('/loginSuccess');
this.props.history.pushState(null, '/loginSuccess');
} else if (this.props.user && !nextProps.user) {
// logout
this.context.router.transitionTo('/');
this.props.history.pushState(null, '/');
}
}

componentWillUnmount() {
const {router} = this.context;
router.removeTransitionHook(this.transitionHook);
}

static fetchData(store) {
const promises = [];
if (!isInfoLoaded(store.getState())) {
Expand Down
2 changes: 1 addition & 1 deletion src/containers/Login/Login.js
Expand Up @@ -24,7 +24,7 @@ export default class Login extends Component {

handleSubmit(event) {
event.preventDefault();
const input = this.refs.username.getDOMNode(); // need for getDOMNode() call going away in React 0.14
const input = this.refs.username;
this.props.login(input.value);
input.value = '';
}
Expand Down
4 changes: 2 additions & 2 deletions src/containers/RequireLogin/RequireLogin.js
Expand Up @@ -2,11 +2,11 @@ import {Component} from 'react';

export default class RequireLogin extends Component {
static onEnter(store) {
return (nextState, transition) => {
return (nextState, replaceState) => {
const { auth: { user }} = store.getState();
if (!user) {
// oops, not logged in, so can't be here!
transition.to('/');
replaceState(null, '/');
}
};
}
Expand Down
3 changes: 2 additions & 1 deletion src/helpers/Html.js
@@ -1,4 +1,5 @@
import React, {Component, PropTypes} from 'react';
import ReactDOM from 'react-dom/server';
import serialize from 'serialize-javascript';
import DocumentMeta from 'react-document-meta';
const cdn = '//cdnjs.cloudflare.com/ajax/libs/';
Expand All @@ -21,7 +22,7 @@ export default class Html extends Component {

render() {
const {assets, component, store} = this.props;
const content = React.renderToString(component);
const content = ReactDOM.renderToString(component);

return (
<html lang="en-us">
Expand Down
69 changes: 37 additions & 32 deletions src/helpers/universalRouter.js
@@ -1,5 +1,6 @@
import qs from 'query-string';
import React from 'react';
import Router from 'react-router';
import {match, RoutingContext} from 'react-router';
import createRoutes from '../routes';
import {Provider} from 'react-redux';

Expand All @@ -9,52 +10,56 @@ const getFetchData = (component = {}) => {
component.fetchData;
};

export function createTransitionHook(store) {
return (nextState, transition, callback) => {
const { params, location: { query } } = nextState;
const promises = nextState.branch
.map(route => route.component) // pull out individual route components
.filter((component) => getFetchData(component)) // only look at ones with a static fetchData()
.map(getFetchData) // pull out fetch data methods
.map(fetchData => fetchData(store, params, query || {})); // call fetch data methods and save promises
Promise.all(promises)
.then(() => {
callback(); // can't just pass callback to then() because callback assumes first param is error
}, (error) => {
callback(error);
});
};
}
const fetchDataForContainers = (containers, store, params, query) => {
const promises = containers
.filter((component) => getFetchData(component)) // only look at ones with a static fetchData()
.map(getFetchData) // pull out fetch data methods
.map(fetchData => fetchData(store, params, query || {})); // call fetch data methods and save promises

return Promise.all(promises);
};

export default function universalRouter(location, history, store) {
export default function universalRouter(location, history, store, preload) {
const routes = createRoutes(store);
return new Promise((resolve, reject) => {
Router.run(routes, location, [createTransitionHook(store)], (error, initialState, transition) => {
match({routes, history, location}, (error, redirectLocation, renderProps) => {
if (error) {
return reject(error);
}

if (transition && transition.redirectInfo) {
if (redirectLocation) {
return resolve({
transition,
isRedirect: true
redirectLocation
});
}

if (history) { // only on client side
initialState.history = history;
renderProps.history = history;
}

const component = (
<Provider store={store} key="provider">
{() => <Router {...initialState} children={routes}/>}
</Provider>
);
function resolveWithComponent() {
const component = (
<Provider store={store} key="provider">
<RoutingContext {...renderProps}/>
</Provider>
);

return resolve({
component,
isRedirect: false
});
resolve({
component
});
}

if (preload) {
fetchDataForContainers(
renderProps.components,
store,
renderProps.params,
qs.parse(renderProps.location.search)
)
.then(() => resolveWithComponent(), err => reject(err));
} else {
resolveWithComponent();
}
});
});
}
17 changes: 9 additions & 8 deletions src/server.js
@@ -1,6 +1,7 @@
import Express from 'express';
import React from 'react';
import Location from 'react-router/lib/Location';
import ReactDOM from 'react-dom/server';
import createLocation from 'history/lib/createLocation'
import config from './config';
import favicon from 'serve-favicon';
import compression from 'compression';
Expand Down Expand Up @@ -52,25 +53,25 @@ app.use((req, res) => {
}
const client = new ApiClient(req);
const store = createStore(client);
const location = new Location(req.path, req.query);
const location = createLocation(req.path, req.query);

const hydrateOnClient = function() {
res.send('<!doctype html>\n' +
React.renderToString(<Html assets={webpackIsomorphicTools.assets()} component={<div/>} store={store}/>));
ReactDOM.renderToString(<Html assets={webpackIsomorphicTools.assets()} component={<div/>} store={store}/>));
}

if (__DISABLE_SSR__) {
hydrateOnClient();
return;
} else {
universalRouter(location, undefined, store)
.then(({component, transition, isRedirect}) => {
if (isRedirect) {
res.redirect(transition.redirectInfo.pathname);
universalRouter(location, undefined, store, true)
.then(({component, redirectLocation}) => {
if (redirectLocation) {
res.redirect(redirectLocation.pathname + redirectLocation.search);
return;
}
res.send('<!doctype html>\n' +
React.renderToString(<Html assets={webpackIsomorphicTools.assets()} component={component} store={store}/>));
ReactDOM.renderToString(<Html assets={webpackIsomorphicTools.assets()} component={component} store={store}/>));
})
.catch((error) => {
if (error.redirect) {
Expand Down

0 comments on commit 5c7bb1d

Please sign in to comment.