Skip to content

Instantly share code, notes, and snippets.

@zspecza
Last active August 29, 2015 14:24
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save zspecza/bf60966f71bfabbd7825 to your computer and use it in GitHub Desktop.
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
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)
@zspecza
Copy link
Author

zspecza commented Jul 11, 2015

Inside any of your components / pages, you can access stuff like /:user in the url as this.props.params.user. Similarly, ?foo=bar&baz=fizz&fuzz will be as follows:

console.log(this.props.query)
/*
  = {
    foo: 'bar',
    baz: ['fizz', 'fuzz']
  }
  */

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment