Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make params available to getChildRoutes providers #3556

Merged
10 changes: 5 additions & 5 deletions docs/API.md
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ A function used to convert an object from [`<Link>`](#link)s or calls to
A function used to convert a query string into an object that gets passed to route component props.

##### `onError(error)`
While the router is matching, errors may bubble up, here is your opportunity to catch and deal with them. Typically these will come from async features like [`route.getComponents`](#getcomponentsnextstate-callback), [`route.getIndexRoute`](#getindexroutelocation-callback), and [`route.getChildRoutes`](#getchildrouteslocation-callback).
While the router is matching, errors may bubble up, here is your opportunity to catch and deal with them. Typically these will come from async features like [`route.getComponents`](#getcomponentsnextstate-callback), [`route.getIndexRoute`](#getindexroutelocation-callback), and [`route.getChildRoutes`](#getchildroutesprogressstate-callback).

##### `onUpdate()`
Called whenever the router updates its state in response to URL changes.
Expand Down Expand Up @@ -368,8 +368,8 @@ A plain JavaScript object route definition. `<Router>` turns JSX `<Route>`s into
##### `childRoutes`
An array of child routes, same as `children` in JSX route configs.

##### `getChildRoutes(location, callback)`
Same as `childRoutes` but asynchronous and receives the `location`. Useful for code-splitting and dynamic route matching (given some state or session data to return a different set of child routes).
##### `getChildRoutes(progressState, callback)`
Same as `childRoutes` but asynchronous and receives the `progressState`. Useful for code-splitting and dynamic route matching (given some state or session data to return a different set of child routes).

###### `callback` signature
`cb(err, routesArray)`
Expand Down Expand Up @@ -399,8 +399,8 @@ let myRoute = {

let myRoute = {
path: 'picture/:id',
getChildRoutes(location, cb) {
let { state } = location
getChildRoutes(progressState, cb) {
let { state } = progressState

if (state && state.fromDashboard) {
cb(null, [dashboardPictureRoute])
Expand Down
4 changes: 2 additions & 2 deletions docs/guides/DynamicRouting.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,15 @@ A router is the perfect place to handle code splitting: it's responsible for set

React Router does all of its [path matching](/docs/guides/RouteMatching.md) and component fetching asynchronously, which allows you to not only load up the components lazily, *but also lazily load the route configuration*. You really only need one route definition in your initial bundle, the router can resolve the rest on demand.

Routes may define [`getChildRoutes`](/docs/API.md#getchildrouteslocation-callback), [`getIndexRoute`](/docs/API.md#getindexroutelocation-callback), and [`getComponents`](/docs/API.md#getcomponentsnextstate-callback) methods. These are asynchronous and only called when needed. We call it "gradual matching". React Router will gradually match the URL and fetch only the amount of route configuration and components it needs to match the URL and render.
Routes may define [`getChildRoutes`](/docs/API.md#getchildroutesprogressstate-callback), [`getIndexRoute`](/docs/API.md#getindexroutelocation-callback), and [`getComponents`](/docs/API.md#getcomponentsnextstate-callback) methods. These are asynchronous and only called when needed. We call it "gradual matching". React Router will gradually match the URL and fetch only the amount of route configuration and components it needs to match the URL and render.

Coupled with a smart code splitting tool like [webpack](http://webpack.github.io/), a once tiresome architecture is now simple and declarative.

```js
const CourseRoute = {
path: 'course/:courseId',

getChildRoutes(location, callback) {
getChildRoutes(progressState, callback) {
require.ensure([], function (require) {
callback(null, [
require('./routes/Announcements'),
Expand Down
2 changes: 1 addition & 1 deletion examples/huge-apps/routes/Course/index.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
module.exports = {
path: 'course/:courseId',

getChildRoutes(location, cb) {
getChildRoutes(progressState, cb) {
require.ensure([], (require) => {
cb(null, [
require('./routes/Announcements'),
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
module.exports = {
path: 'announcements',

getChildRoutes(location, cb) {
getChildRoutes(progressState, cb) {
require.ensure([], (require) => {
cb(null, [
require('./routes/Announcement')
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
module.exports = {
path: 'assignments',

getChildRoutes(location, cb) {
getChildRoutes(progressState, cb) {
require.ensure([], (require) => {
cb(null, [
require('./routes/Assignment')
Expand Down
4 changes: 2 additions & 2 deletions modules/__tests__/matchRoutes-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -262,7 +262,7 @@ describe('matchRoutes', function () {
if (childRoutes) {
delete route.childRoutes

route.getChildRoutes = function (location, callback) {
route.getChildRoutes = function (progressState, callback) {
setTimeout(function () {
callback(null, childRoutes)
})
Expand Down Expand Up @@ -294,7 +294,7 @@ describe('matchRoutes', function () {
let getChildRoutes, getIndexRoute, jsxRoutes

beforeEach(function () {
getChildRoutes = function (location, callback) {
getChildRoutes = function (progressState, callback) {
setTimeout(function () {
callback(null, <Route path=":userID" />)
})
Expand Down
31 changes: 31 additions & 0 deletions modules/deprecateLocationProperties.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import warning from './routerWarning'
import { canUseMembrane } from './deprecateObjectProperties'

// No-op by default.
let deprecateLocationProperties = () => {}

if (__DEV__ && canUseMembrane) {
deprecateLocationProperties = (nextState, location) => {
const nextStateWithLocation = { ...nextState }

// I don't use deprecateObjectProperties here because I want to keep the
// same code path between development and production, in that we just
// assign extra properties to the copy of the state object in both cases.
for (const prop in location) {
if (!Object.prototype.hasOwnProperty.call(location, prop)) {
continue
}

Object.defineProperty(nextStateWithLocation, prop, {
get() {
warning(false, 'Accessing location properties from the first argument to `getComponent` and `getComponents` is deprecated. That argument is now the router state (`nextState`) rather than the location. To access the location, use `nextState.location`.')
return location[prop]
}
})
}

return nextStateWithLocation
}
}

export default deprecateLocationProperties
20 changes: 2 additions & 18 deletions modules/getComponents.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { mapAsync } from './AsyncUtils'
import { canUseMembrane } from './deprecateObjectProperties'
import warning from './routerWarning'
import deprecateLocationProperties from './deprecateLocationProperties'

function getComponentsForRoute(nextState, route, callback) {
if (route.component || route.components) {
Expand All @@ -18,23 +18,7 @@ function getComponentsForRoute(nextState, route, callback) {
let nextStateWithLocation

if (__DEV__ && canUseMembrane) {
nextStateWithLocation = { ...nextState }

// I don't use deprecateObjectProperties here because I want to keep the
// same code path between development and production, in that we just
// assign extra properties to the copy of the state object in both cases.
for (const prop in location) {
if (!Object.prototype.hasOwnProperty.call(location, prop)) {
continue
}

Object.defineProperty(nextStateWithLocation, prop, {
get() {
warning(false, 'Accessing location properties from the first argument to `getComponent` and `getComponents` is deprecated. That argument is now the router state (`nextState`) rather than the location. To access the location, use `nextState.location`.')
return location[prop]
}
})
}
nextStateWithLocation = deprecateLocationProperties(nextState, location)
} else {
nextStateWithLocation = { ...nextState, ...location }
}
Expand Down
20 changes: 16 additions & 4 deletions modules/matchRoutes.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,30 @@ import warning from './routerWarning'
import { loopAsync } from './AsyncUtils'
import { matchPattern } from './PatternUtils'
import { createRoutes } from './RouteUtils'
import { canUseMembrane } from './deprecateObjectProperties'
import deprecateLocationProperties from './deprecateLocationProperties'

function getChildRoutes(route, location, callback) {
function getChildRoutes(route, location, paramNames, paramValues, remainingPathname, callback) {
if (route.childRoutes) {
return [ null, route.childRoutes ]
}
if (!route.getChildRoutes) {
return []
}

let sync = true, result
let sync = true, result, progressStateWithLocation
const progressState = {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would call this partialNextState – we use the term elsewhere for partial next state.

Can we omit remainingPathname here? I don't think it makes sense to pass down at this point. It feels odd for child routes to explicitly depend on the not-yet-matched portion of a route.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

partialNextState is fine.

params: createParams(paramNames, paramValues),
remainingPathname
}

if (__DEV__ && canUseMembrane) {
progressStateWithLocation = deprecateLocationProperties(progressState, location)
} else {
progressStateWithLocation = { ...progressState, ...location }
}

route.getChildRoutes(location, function (error, childRoutes) {
route.getChildRoutes(progressStateWithLocation, function (error, childRoutes) {
childRoutes = !error && createRoutes(childRoutes)
if (sync) {
result = [ error, childRoutes ]
Expand Down Expand Up @@ -160,7 +172,7 @@ function matchRouteDeep(
}
}

const result = getChildRoutes(route, location, onChildRoutes)
const result = getChildRoutes(route, location, paramNames, paramValues, remainingPathname, onChildRoutes)
if (result) {
onChildRoutes(...result)
}
Expand Down