Skip to content

Instantly share code, notes, and snippets.

@jquense
Created September 14, 2015 04:56
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save jquense/109f86a6b443345bfd76 to your computer and use it in GitHub Desktop.
Save jquense/109f86a6b443345bfd76 to your computer and use it in GitHub Desktop.
React router with areas and names
import React from 'react';
import { Router as BaseRouter } from 'react-router';
import { getParamNames, formatPattern } from 'react-router/lib/PatternUtils';
import { createRoutes } from 'react-router/lib/RouteUtils'
import createBrowserHistory from 'history/lib/createBrowserHistory';
import useBeforeUnload from 'history/lib/useBeforeUnload';
import invariant from 'app/utils/invariant';
import { each, pick, omit, sortBy } from 'lodash';
const EMPTY_AREA = '@@none';
const routeHash = Object.create(null);
let history = useBeforeUnload(createBrowserHistory)();
export let router;
export function routeUrl({ area = EMPTY_AREA, name, ...params }){
invariant(routeHash[area],
'the provided area: ' + area + ' is not valid. ' +
'Valid areas are: ' + Object.keys(routeHash).filter( n => n !== EMPTY_AREA).join(', '));
let routes = routeHash[area][name] || [];
invariant(routes.length,
'the provided name: ' + name + ' is not valid. ' +
'Valid names are: ' + Object.keys(routeHash[area]).join(', '));
let candidates = routes
.map(route => {
let names = getParamNames(route);
return {
route,
needed: names.length,
params: pick(params, names),
query: omit(params, names)
}
})
.filter(i => Object.keys(i.params).length >= i.needed)
invariant(candidates.length,
'No routes matched the provided infomation, you may be missing a parameter')
let match = sortBy(candidates, i => i.needed.length)[0];
return formatPattern(match.route, match.params)
}
export function isActive(name){
let routes = router && router.state.routes;
return routes && routes.some(route => route.name === name)
}
export let Router = React.createClass({
componentWillMount(){
this._routes = createRoutes(this.props.children);
buildHash(this._routes)
},
render(){
return (
<BaseRouter
ref={r => router = r}
history={history}
{...this.props}
children={this._routes}
/>
)
}
})
function combinePath(path, parent){
if (path[0] === '/') return path
return parent ? (parent + '/' + path) : ('/' + path)
}
function buildHash(routes, parentPath, parentArea) {
forEach(routes, (name, area, path, { childRoutes, getChildRoutes, providesPaths }) => {
path = combinePath(path, parentPath)
if (name){
invariant(!parentArea || (parentArea && !area),
`the route: ${name}: '${path}' cannot specify an area if it is inside an area already`);
area = parentArea || area || EMPTY_AREA;
routeHash[area] = routeHash[area] || {};
routeHash[area][name] = (routeHash[area][name] || []).concat(path);
}
if ( getChildRoutes ) {
invariant(providesPaths,
'Lazy loading routes requires that you specify the paths in `providesPaths`')
buildHash(providesPaths, path, area)
}
if (childRoutes)
buildHash(childRoutes, path, area)
})
}
function forEach(routeOrHash, cb){
each(routeOrHash, (child, key) => {
var name, area, path, props;
if (!child) return;
else if (typeof child === 'string')
name = key, path = child;
else
({ name, area, path, ...props } = child)
cb(name, area, path, props || {})
})
}
@jquense
Copy link
Author

jquense commented Sep 14, 2015

and then you can use routes like:

let calendarComponents = { toolbar: CalendarToolbar, main: Calendar };

export default (
  <Route path='Event' area='events'>
    <Route name='events' path="Case/:caseID" component={Page}>
      <IndexRoute components={calendarComponents}/>
      <Route name='calendar' path="Calendar" components={calendarComponents}/>
      <Route name='event_details' path="Events/:eventID"/>
      <Route name='search' path="Search"/>
    </Route>
  </Route>
)

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