Skip to content

Commit

Permalink
Use react-router; client- and server side rendering
Browse files Browse the repository at this point in the history
  • Loading branch information
okendoken committed Jan 31, 2016
1 parent d496698 commit bed5f5d
Show file tree
Hide file tree
Showing 13 changed files with 134 additions and 197 deletions.
2 changes: 1 addition & 1 deletion package.json
Expand Up @@ -21,7 +21,7 @@
"normalize.css": "3.0.3",
"react": "0.14.7",
"react-dom": "0.14.7",
"react-routing": "0.0.7",
"react-router": "2.0.0-rc5",
"source-map-support": "0.4.0",
"whatwg-fetch": "0.11.0"
},
Expand Down
80 changes: 41 additions & 39 deletions src/client.js
Expand Up @@ -8,10 +8,13 @@
*/

import 'babel-polyfill';
import ReactDOM from 'react-dom';
import React from 'react';
import { match, Router } from 'react-router';
import { render } from 'react-dom';
import FastClick from 'fastclick';
import Router from './routes';
import routes from './routes';
import Location from './core/Location';
import ContextHolder from './core/ContextHolder';
import { addEventListener, removeEventListener } from './core/DOMUtils';

let cssContainer = document.getElementById('css');
Expand All @@ -35,57 +38,56 @@ const context = {
},
};

function render(state) {
Router.dispatch(state, (newState, component) => {
ReactDOM.render(component, appContainer, () => {
// Restore the scroll position if it was saved into the state
if (state.scrollY !== undefined) {
window.scrollTo(state.scrollX, state.scrollY);
} else {
window.scrollTo(0, 0);
}

// Remove the pre-rendered CSS because it's no longer used
// after the React app is launched
if (cssContainer) {
cssContainer.parentNode.removeChild(cssContainer);
cssContainer = null;
}
});
});
}

function run() {
let currentLocation = null;
let currentState = null;
const scrollOffsets = new Map();
let currentScrollOffset = null;

// Make taps on links and buttons work fast on mobiles
FastClick.attach(document.body);

// Re-render the app when window.location changes
const unlisten = Location.listen(location => {
currentLocation = location;
currentState = Object.assign({}, location.state, {
path: location.pathname,
query: location.query,
state: location.state,
context,
});
render(currentState);
const locationId = location.pathname + location.search;
if (!scrollOffsets.get(locationId)) {
scrollOffsets.set(locationId, Object.create(null));
}
currentScrollOffset = scrollOffsets.get(locationId);
// Restore the scroll position if it was saved
if (currentScrollOffset.scrollY !== undefined) {
window.scrollTo(currentScrollOffset.scrollX, currentScrollOffset.scrollY);
} else {
window.scrollTo(0, 0);
}
});

const { pathname, search, hash } = window.location;
const location = `${pathname}${search}${hash}`;

match({ routes, location }, (error, redirectLocation, renderProps) => {
render(
<ContextHolder context={context}>
<Router {...renderProps} children={routes} history={Location} />
</ContextHolder>,
appContainer
);
// Remove the pre-rendered CSS because it's no longer used
// after the React app is launched
if (cssContainer) {
cssContainer.parentNode.removeChild(cssContainer);
cssContainer = null;
}
});

// Save the page scroll position into the current location's state
// Save the page scroll position
const supportPageOffset = window.pageXOffset !== undefined;
const isCSS1Compat = ((document.compatMode || '') === 'CSS1Compat');
const setPageOffset = () => {
currentLocation.state = currentLocation.state || Object.create(null);
if (supportPageOffset) {
currentLocation.state.scrollX = window.pageXOffset;
currentLocation.state.scrollY = window.pageYOffset;
currentScrollOffset.scrollX = window.pageXOffset;
currentScrollOffset.scrollY = window.pageYOffset;
} else {
currentLocation.state.scrollX = isCSS1Compat ?
currentScrollOffset.scrollX = isCSS1Compat ?
document.documentElement.scrollLeft : document.body.scrollLeft;
currentLocation.state.scrollY = isCSS1Compat ?
currentScrollOffset.scrollY = isCSS1Compat ?
document.documentElement.scrollTop : document.body.scrollTop;
}
};
Expand Down
26 changes: 3 additions & 23 deletions src/components/App/App.js
Expand Up @@ -8,7 +8,6 @@
*/

import React, { Component, PropTypes } from 'react';
import emptyFunction from 'fbjs/lib/emptyFunction';
import s from './App.scss';
import Header from '../Header';
import Feedback from '../Feedback';
Expand All @@ -17,35 +16,16 @@ import Footer from '../Footer';
class App extends Component {

static propTypes = {
context: PropTypes.shape({
insertCss: PropTypes.func,
onSetTitle: PropTypes.func,
onSetMeta: PropTypes.func,
onPageNotFound: PropTypes.func,
}),
children: PropTypes.element.isRequired,
error: PropTypes.object,
};

static childContextTypes = {
insertCss: PropTypes.func.isRequired,
onSetTitle: PropTypes.func.isRequired,
onSetMeta: PropTypes.func.isRequired,
onPageNotFound: PropTypes.func.isRequired,
static contextTypes = {
insertCss: PropTypes.func,
};

getChildContext() {
const context = this.props.context;
return {
insertCss: context.insertCss || emptyFunction,
onSetTitle: context.onSetTitle || emptyFunction,
onSetMeta: context.onSetMeta || emptyFunction,
onPageNotFound: context.onPageNotFound || emptyFunction,
};
}

componentWillMount() {
this.removeCss = this.props.context.insertCss(s);
this.removeCss = this.context.insertCss(s);
}

componentWillUnmount() {
Expand Down
2 changes: 1 addition & 1 deletion src/components/Footer/Footer.js
Expand Up @@ -10,7 +10,7 @@
import React, { Component } from 'react';
import withStyles from 'isomorphic-style-loader/lib/withStyles';
import s from './Footer.scss';
import Link from '../Link';
import { Link } from 'react-router';

class Footer extends Component {

Expand Down
6 changes: 3 additions & 3 deletions src/components/Header/Header.js
Expand Up @@ -10,7 +10,7 @@
import React, { Component } from 'react';
import withStyles from 'isomorphic-style-loader/lib/withStyles';
import s from './Header.scss';
import Link from '../Link';
import { IndexLink } from 'react-router';
import Navigation from '../Navigation';

class Header extends Component {
Expand All @@ -20,10 +20,10 @@ class Header extends Component {
<div className={s.root}>
<div className={s.container}>
<Navigation className={s.nav} />
<Link className={s.brand} to="/">
<IndexLink className={s.brand} to="/">
<img src={require('./logo-small.png')} width="38" height="38" alt="React" />
<span className={s.brandTxt}>Your Company</span>
</Link>
</IndexLink>
<div className={s.banner}>
<h1 className={s.bannerTitle}>React</h1>
<p className={s.bannerDesc}>Complex web apps made easy</p>
Expand Down
4 changes: 2 additions & 2 deletions src/components/Html/Html.js
Expand Up @@ -49,9 +49,9 @@ class Html extends Component {
<style id="css" dangerouslySetInnerHTML={{ __html: this.props.css }} />
</head>
<body>
<div id="app" dangerouslySetInnerHTML={{ __html: this.props.body }} />
<div id="app" dangerouslySetInnerHTML={{ __html: this.props.body }} ></div>
<script src={this.props.entry}></script>
<script dangerouslySetInnerHTML={this.trackingCode()} />
<script dangerouslySetInnerHTML={this.trackingCode()} ></script>
</body>
</html>
);
Expand Down
73 changes: 0 additions & 73 deletions src/components/Link/Link.js

This file was deleted.

6 changes: 0 additions & 6 deletions src/components/Link/package.json

This file was deleted.

2 changes: 1 addition & 1 deletion src/components/Navigation/Navigation.js
Expand Up @@ -11,7 +11,7 @@ import React, { Component, PropTypes } from 'react';
import cx from 'classnames';
import withStyles from 'isomorphic-style-loader/lib/withStyles';
import s from './Navigation.scss';
import Link from '../Link';
import { Link } from 'react-router';

class Navigation extends Component {

Expand Down
38 changes: 38 additions & 0 deletions src/core/ContextHolder.js
@@ -0,0 +1,38 @@
import React, { PropTypes } from 'react';
import emptyFunction from 'fbjs/lib/emptyFunction';

class ContextHolder extends React.Component {

static propTypes = {
context: PropTypes.shape({
insertCss: PropTypes.func,
onSetMeta: PropTypes.func,
onPageNotFound: PropTypes.func,
}),
children: PropTypes.element.isRequired,
};

static childContextTypes = {
insertCss: PropTypes.func,
onSetTitle: PropTypes.func,
onSetMeta: PropTypes.func,
onPageNotFound: PropTypes.func,
};

getChildContext() {
const context = this.props.context;
return {
insertCss: context.insertCss || emptyFunction,
onSetTitle: context.onSetTitle || emptyFunction,
onSetMeta: context.onSetMeta || emptyFunction,
onPageNotFound: context.onPageNotFound || emptyFunction,
};
}

render() {
const { children } = this.props;
return React.Children.only(children);
}
}

export default ContextHolder;
6 changes: 2 additions & 4 deletions src/core/Location.js
Expand Up @@ -8,10 +8,8 @@
*/

import { canUseDOM } from 'fbjs/lib/ExecutionEnvironment';
import createHistory from 'history/lib/createBrowserHistory';
import createMemoryHistory from 'history/lib/createMemoryHistory';
import useQueries from 'history/lib/useQueries';
import { browserHistory, createMemoryHistory } from 'react-router';

const location = useQueries(canUseDOM ? createHistory : createMemoryHistory)();
const location = canUseDOM ? browserHistory : createMemoryHistory();

export default location;

0 comments on commit bed5f5d

Please sign in to comment.