Skip to content

Add redirect, props, forcedProps to react-router-config route keys and inject renderChild function prop #6170

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

Closed
wants to merge 20 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
7d03df5
Add redirect, props, forcedProps to react-router-config route keys
jharris4 May 24, 2018
e3f8185
fixup formatting of README for redirect/props/forcedProps changes
jharris4 May 24, 2018
6e5ff60
update react-router-config package-lock.json for react-router 4.3.x
jharris4 May 24, 2018
4ab32a3
wrap the renderRoutes Redirect in a Route and use generatePath to pre…
jharris4 May 24, 2018
6147696
pass the params, not the match to generatePath
jharris4 May 24, 2018
2b8c7d4
Add renderChild function prop and allow it and routeProp to be config…
jharris4 May 24, 2018
cb005bc
add docs about the merge order for route component props
jharris4 May 24, 2018
4c3b063
improve docs for route component props order
jharris4 May 24, 2018
6c3394b
revert previous package-lock change
jharris4 May 24, 2018
5021ba8
react-router-config peer dependency ^4.3.0-rc.3 -> ^4.2.0 || ^4.3.0-rc.3
jharris4 May 24, 2018
cf048bb
add externals/globals for generatePath
jharris4 May 24, 2018
463dfb2
update react-router-config package-lock.json for react-router 4.3.0-rc.3
jharris4 May 24, 2018
d1be8a0
add support for routes without a component (child routes are enderered)
jharris4 May 24, 2018
fb3ec6b
revert all package.json and package-lock.json changes
jharris4 May 25, 2018
ca11b7b
finally get the build and tests to pass from the mono repo root
jharris4 May 29, 2018
722d45d
merge latest from react-router repo
jharris4 Jun 7, 2018
5787a75
add addParamProps option as a convenience to inject all match.params.…
jharris4 Jun 7, 2018
9a6847e
fix typo
jharris4 Jun 7, 2018
0ae2dc9
update README for addParamProps option
jharris4 Jun 7, 2018
7ef0a11
merge package.json changes from master
jharris4 Sep 27, 2018
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
221 changes: 180 additions & 41 deletions packages/react-router-config/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ Static route configuration helpers for React Router.

This is alpha software, it needs:

1. Realistic server rendering example with data preloading
2. Pending navigation example
1. Realistic server rendering example with data preloading
2. Pending navigation example

## Installation

Expand All @@ -17,10 +17,10 @@ Then with a module bundler like [webpack](https://webpack.github.io/), use as yo

```js
// using an ES6 transpiler, like babel
import { matchRoutes, renderRoutes } from 'react-router-config'
import { matchRoutes, renderRoutes } from "react-router-config";

// not using an ES6 transpiler
var matchRoutes = require('react-router-config').matchRoutes
var matchRoutes = require("react-router-config").matchRoutes;
```

The UMD build is also available on [unpkg](https://unpkg.com):
Expand All @@ -35,40 +35,50 @@ You can find the library on `window.ReactRouterConfig`

With the introduction of React Router v4, there is no longer a centralized route configuration. There are some use-cases where it is valuable to know about all the app's potential routes such as:

- Loading data on the server or in the lifecycle before rendering the next screen
- Linking to routes by name
- Static analysis
* Loading data on the server or in the lifecycle before rendering the next screen
* Linking to routes by name
* Static analysis

This project seeks to define a shared format for others to build patterns on top of.

## Route Configuration Shape

Routes are objects with the same properties as a `<Route>` with a couple differences:

- the only render prop it accepts is `component` (no `render` or `children`)
- introduces the `routes` key for sub routes
- Consumers are free to add any additional props they'd like to a route, you can access `props.route` inside the `component`, this object is a reference to the object used to render and match.
- accepts `key` prop to prevent remounting component when transition was made from route with the same component and same `key` prop
* the only render prop it accepts is `component` (no `render` or `children`)
* If no `component` is provided but `routes` are provided, then the child routes will still be rendered.
* accepts `key` prop to prevent remounting component when transition was made from route with the same component and same `key` prop
* introduces the `routes` key for sub routes
* introduces the `redirect` key which can be a path that should be redirected to (with parameter matching) when the route is matched
* introduces the `props` and `forcedProps` keys, which can be used for convenience to pass props from the route configuration into the route component
* Consumers are free to add any additional props they'd like to a route
* The `route` and `match` are passed as props to the `component` (prop names configurable)
* A convenience `renderChild` function is passed as a prop to the `component` (prop name configurable).
* The `addParamProps` option can be used to directly inject all values in `match.params` as props.

```js
const routes = [
{ component: Root,
{
component: Root,
routes: [
{ path: '/',
{
path: "/",
exact: true,
component: Home
},
{ path: '/child/:id',
{
path: "/child/:id",
component: Child,
routes: [
{ path: '/child/:id/grand-child',
{
path: "/child/:id/grand-child",
component: GrandChild
}
]
}
]
}
]
];
```

**Note**: Just like `<Route>`, relative paths are not (yet) supported. When it is supported there, it will be supported here.
Expand All @@ -80,12 +90,13 @@ const routes = [
Returns an array of matched routes.

#### Parameters
- routes - the route configuration
- pathname - the [pathname](https://developer.mozilla.org/en-US/docs/Web/API/HTMLHyperlinkElementUtils/pathname) component of the url. This must be a decoded string representing the path.

* routes - the route configuration
* pathname - the [pathname](https://developer.mozilla.org/en-US/docs/Web/API/HTMLHyperlinkElementUtils/pathname) component of the url. This must be a decoded string representing the path.

```js
import { matchRoutes } from 'react-router-config'
const branch = matchRoutes(routes, '/child/23')
import { matchRoutes } from "react-router-config";
const branch = matchRoutes(routes, "/child/23");
// using the routes shown earlier, this returns
// [
// routes[0],
Expand All @@ -95,12 +106,12 @@ const branch = matchRoutes(routes, '/child/23')

Each item in the array contains two properties: `routes` and `match`.

- `routes`: A reference to the routes array used to match
- `match`: The match object that also gets passed to `<Route>` render methods.
* `routes`: A reference to the routes array used to match
* `match`: The match object that also gets passed to `<Route>` render methods.

```js
branch[0].match.url
branch[0].match.isExact
branch[0].match.url;
branch[0].match.isExact;
// etc.
```

Expand Down Expand Up @@ -186,68 +197,196 @@ import routes from './routes'

Again, that's all pseudo-code. There are a lot of ways to do server rendering with data and pending navigation and we haven't settled on one. The point here is that `matchRoutes` gives you a chance to match statically outside of the render lifecycle. We'd like to make a demo app of this approach eventually.

### `renderRoutes(routes, extraProps = {}, switchProps = {})`
### `renderRoutes(routes, { extraProps = {}, switchProps = {}, routeProp = 'route', renderChildProp = 'renderChild' } = {})`

In order to ensure that matching outside of render with `matchRoutes` and inside of render result in the same branch, you must use `renderRoutes` instead of `<Route>` inside your components. You can render a `<Route>` still, but know that it will not be accounted for in `matchRoutes` outside of render.
In order to ensure that matching outside of render with `matchRoutes` and inside of render result in the same branch, you must use `renderRoutes` or `renderChild` instead of `<Route>` inside your components. You can render a `<Route>` still, but know that it will not be accounted for in `matchRoutes` outside of render.

```js
import { renderRoutes } from 'react-router-config'
import { renderRoutes } from "react-router-config";

const routes = [
{ component: Root,
{
component: Root,
routes: [
{ path: '/',
{
path: "/",
exact: true,
component: Home
},
{ path: '/child/:id',
{
path: "/other:id",
redirect: "/child:id"
},
{
path: "/child/:id",
component: Child,
props: {
className: "child-css-class"
},
routes: [
{ path: '/child/:id/grand-child',
{
path: "/child/:id/grand-child",
component: GrandChild
}
]
}
]
}
]
];

const Root = ({ route }) => (
<div>
<h1>Root</h1>
{/* child routes won't render without this */}
{renderRoutes(route.routes)}
</div>
)
);

const Home = ({ route }) => (
<div>
<h2>Home</h2>
</div>
)
);

const Child = ({ route }) => (
<div>
<h2>Child</h2>
{/* child routes won't render without this */}
{renderRoutes(route.routes, { someProp: 'these extra props are optional' })}
{renderRoutes(route.routes, { someProp: "these extra props are optional" })}
</div>
)
);

const GrandChild = ({ someProp }) => (
<div>
<h3>Grand Child</h3>
<div>{someProp}</div>
</div>
)
);


ReactDOM.render((
ReactDOM.render(
<BrowserRouter>
{/* kick it all off with the root route */}
{renderRoutes(routes)}
</BrowserRouter>
), document.getElementById('root'))
</BrowserRouter>,
document.getElementById("root")
);
```

Or you can update the above examples to use `props.renderChild` which eleminates the need to import the `renderRoutes` function

```js
const Root = ({ renderChild }) => (
<div>
<h1>Root</h1>
{/* child routes won't render without this */}
{renderChild()}
</div>
);

const Home = ({ route, renderChild }) => (
<div>
<h2>Home</h2>
</div>
);

const Child = ({ renderChild }) => (
<div>
<h2>Child</h2>
{/* child routes won't render without this */}
{renderChild({ someProp: "these extra props are optional" })}
</div>
);

const GrandChild = ({ someProp }) => (
<div>
<h3>Grand Child</h3>
<div>{someProp}</div>
</div>
);
```

## Route Component Prop Order

When route `components` are rendered they are passed a collection of props that are merged in a specific order.

The are `route` keys that allow you to control this merge order.

* Use `route.props` for props that should be overriden by props passed to the component
* Use `route.forcedProps` for props that should override any props passed to the component

This merge order is important if you are trying to do something like the following:

```
const routes = [
{
props: {
className: 'the-app-theme'
},
routes: [
{
path: '/abc',
routes: [
{
props: {
className: 'the-abc-theme' // will always be overriden by 'the-app-theme'
}
}
]
}
]
}
];
```

You need to instead do the following for the className to be correctly applied:

```
const routes = [
{
props: {
className: 'the-app-theme'
},
routes: [
{
path: '/abc',
routes: [
{
forcedProps: {
className: 'the-abc-theme' // will override 'the-app-theme'
}
}
]
}
]
}
];
```

The following illustrates the merge order of route component props:

```js
const {
extraProps = {},
switchProps = {},
routeProp = "route",
matchProp = "match",
renderChildProp = "renderChild",
addParamProps = false,
noRenderChildComponent = () => false,
overrideRenderRoutes = false
} = (renderRoutesOptions = {});

const mergeRouteProps = (route, props) => {
const { route: stripRoute, match, ...otherProps } = props;
return {
...(route.props ? route.props : {}),
...otherProps,
...extraProps,
...(route.forcedProps ? route.forcedProps : {}),
...(routeProp ? { [routeProp]: route } : {}),
...(matchProp ? { [matchProp]: match } : {}),
...(addParamProps ? (match ? (match.params ? match.params : {}) : {}) : {}),
...(renderChildProp ? { [renderChildProp]: renderChild(route) } : {})
};
};
```
Loading