diff --git a/modules/Router.js b/modules/Router.js index e487a2f323..dd2787480b 100644 --- a/modules/Router.js +++ b/modules/Router.js @@ -19,6 +19,7 @@ class Router extends Component { history: object, children: routes, routes, // alias for children + RoutingContext: func.isRequired, createElement: func, onError: func, onUpdate: func, @@ -26,6 +27,10 @@ class Router extends Component { stringifyQuery: func } + static defaultProps = { + RoutingContext + } + constructor(props, context) { super(props, context) @@ -80,12 +85,17 @@ class Router extends Component { render() { let { location, routes, params, components } = this.state - let { createElement } = this.props + let { RoutingContext, createElement, ...props } = this.props if (location == null) return null // Async match + // Only forward non-Router-specific props to routing context, as those are + // the only ones that might be custom routing context props. + Object.keys(Router.propTypes).forEach(propType => delete props[propType]) + return React.createElement(RoutingContext, { + ...props, history: this.history, createElement, location, diff --git a/modules/__tests__/Router-test.js b/modules/__tests__/Router-test.js index 97d2b82663..8f469e6852 100644 --- a/modules/__tests__/Router-test.js +++ b/modules/__tests__/Router-test.js @@ -2,8 +2,9 @@ import expect from 'expect' import React, { Component } from 'react' import { render, unmountComponentAtNode } from 'react-dom' import createHistory from 'history/lib/createMemoryHistory' -import Router from '../Router' import Route from '../Route' +import Router from '../Router' +import RoutingContext from '../RoutingContext' describe('Router', function () { @@ -248,4 +249,98 @@ describe('Router', function () { }) + describe('RoutingContext', function () { + it('applies custom RoutingContext', function (done) { + const Parent = ({ children }) => parent:{children} + const Child = () => child + + class LabelWrapper extends Component { + static defaultProps = { + createElement: React.createElement + } + + createElement = (component, props) => { + const { label, createElement } = this.props + + return ( + + {label}-inner:{createElement(component, props)} + + ) + } + + render() { + const { label, children } = this.props + const child = React.cloneElement(children, { + createElement: this.createElement + }) + + return ( + + {label}-outer:{child} + + ) + } + } + + const CustomRoutingContext = props => ( + + + + + + ) + + render(( + + + + + + ), node, function () { + // Note that the nesting order is inverted for `createElement`, because + // the order of function application is outermost-first. + expect(node.textContent).toBe( + 'm1-outer:m2-outer:m2-inner:m1-inner:parent:m2-inner:m1-inner:child' + ) + done() + }) + }) + + it('passes router props to custom RoutingContext', function (done) { + const MyComponent = () =>
+ const route = { path: '/', component: MyComponent } + + const Wrapper = ( + { routes, components, foo, RoutingContext, children } + ) => { + expect(routes).toEqual([ route ]) + expect(components).toEqual([ MyComponent ]) + expect(foo).toBe('bar') + expect(RoutingContext).toNotExist() + done() + + return children + } + const CustomRoutingContext = props => ( + + + + ) + + render(( + + ), node) + }) + + }) + })