Last active
August 29, 2015 14:24
-
-
Save zspecza/bf60966f71bfabbd7825 to your computer and use it in GitHub Desktop.
A simple, isomorphic, state-based routing component wrapper around Page.js for React
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import page from 'page' | |
import pathToRegExp from 'path-to-regexp' | |
import querystring from 'querystring' | |
import HomePage from './components/HomePage' | |
import ProfilePage from './components/ProfilePage' | |
import NotFoundPage from './components/NotFoundPage' | |
// generate expression for iterating over object keys | |
function entries(obj) { | |
return (for (key of Object.keys(obj)) [key, obj[key]]) | |
} | |
const { | |
Component | |
, PropTypes | |
} = React | |
class ReactPage extends Component { | |
// basic prop validation | |
static propTypes = { | |
url : PropTypes.string.isRequired | |
, routes : PropTypes.shape({ | |
'*': PropTypes.element.isRequired | |
}) | |
} | |
// we need to get the initital handler to render | |
// in the case of server-side rendering | |
// so that we aren't rendering an empty page initially | |
state = { | |
component: (() => { // IIFE | |
let MatchedHandler = null | |
for (let [route, Handler] of entries(this.props.routes)) { | |
const re = pathToRegExp(route, []) | |
if (re.exec(this.props.url)) { // check if the requested url matches | |
MatchedHandler = Handler | |
break // break out of this loop if we found a handler | |
} | |
} | |
if (MatchedHandler != null) | |
return MatchedHandler | |
else | |
throw new Error( | |
'[FATAL]: Could not load route. ' + | |
'You might be missing a NotFound route handler.' | |
) | |
})() | |
} | |
componentDidMount() { | |
// on initial render, we need to attach `query` | |
// so that we can access query params as `this.props.query` | |
page((context, next) => { | |
context.query = querystring.parse(context.querystring) | |
next() // move on | |
}) | |
// iterate over each route, and attach a page.js route callback | |
// that renders the correct handler by setting `setState` | |
for (let [route, Handler] of entries(this.props.routes)) { | |
page(route, context => this.setState({ | |
component: <Handler {...context} /> | |
})) | |
} | |
// same as above, except we just register redirects from | |
// each redirectRoute | |
for (let [from, to] of entries(this.props.redirectRoutes)) { | |
page(from, to) | |
} | |
// finally, we start page.js, but use the `dispatch` option | |
// to prevent it from triggering a route on initial load | |
// (the server should handle rendering the correct handler for us) | |
// pagejs should navigate automaticaly when links are clicked by default | |
// so we need to disable this with the `click` opt so we can manage | |
// it ourselves with state | |
page({ | |
dispatch : false | |
}) | |
} | |
render() { | |
return this.state.component | |
} | |
} | |
// wrap the page in our own root element - we can handle | |
// how we pass state / props down however we want to this way | |
class Root extends Component { | |
state = { | |
url: '/' | |
, routes: { | |
'/' : HomePage | |
, '/user/:id' : ProfilePage | |
, '*' : NotFoundPage | |
} | |
, redirectRoutes: { | |
'/from' : '/to' | |
} | |
} | |
_navigate(url) { | |
this.setState({ url }) | |
} | |
render() { | |
return ( | |
<div> | |
<h1>Welcome to my isomorphic page with client-side state-based routing</h1> | |
<a href='/user/1' onClick={this._navigate('/user/1')}>Go to my profile</a> | |
<br /> | |
<ReactPage {...this.state} /> // pass state down as props | |
</div> | |
) | |
} | |
} | |
React.render(<Root />, document.body) | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Inside any of your components / pages, you can access stuff like
/:user
in the url asthis.props.params.user
. Similarly,?foo=bar&baz=fizz&fuzz
will be as follows: