Skip to content

Commit 39a87cb

Browse files
timdorrtaion
authored andcommitted
Add withRouter HoC (#3352)
* Create a withRouter HoC. * withRouter passes this.props.router * Update docs, examples, and upgrade guide. Also add an export of withRouter * Doc tweaks. * Remove displayNames.
1 parent 70c23b4 commit 39a87cb

File tree

16 files changed

+369
-335
lines changed

16 files changed

+369
-335
lines changed

docs/API.md

Lines changed: 4 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
- [`<Router>`](#router)
55
- [`<Link>`](#link)
66
- [`<IndexLink>`](#indexlink)
7+
- [`withRouter`](#withRouter-component)
78
- [`<RouterContext>`](#routercontext)
89
- [`context.router`](#contextrouter)
910
- `<RoutingContext>` (deprecated, use `<RouterContext>`)
@@ -162,6 +163,9 @@ Given a route like `<Route path="/users/:userId" />`:
162163
### `<IndexLink>`
163164
An `<IndexLink>` is like a [`<Link>`](#link), except it is only active when the current route is exactly the linked route. It is equivalent to `<Link>` with the `onlyActiveOnIndex` prop set.
164165

166+
### `withRouter(component)`
167+
A HoC (higher-order component) that wraps another component to provide `this.props.router`. Pass in your component and it will return the wrapped component.
168+
165169
### `<RouterContext>`
166170
A `<RouterContext>` renders the component tree for a given router state. Its used by `<Router>` but also useful for server rendering and integrating in brownfield development.
167171

@@ -171,61 +175,6 @@ It also provides a `router` object on [context](https://facebook.github.io/react
171175

172176
Contains data and methods relevant to routing. Most useful for imperatively transitioning around the application.
173177

174-
To use it, you must signal to React that you need it by declaring your use of it in your component via `contextTypes`:
175-
176-
```js
177-
var MyComponent = React.createClass({
178-
contextTypes: {
179-
router: routerShape.isRequired
180-
},
181-
182-
render: function() {
183-
// Here, you can use this.context.router.
184-
}
185-
})
186-
```
187-
188-
To use `context.router` on a component declared as an ES2015 class, define `contextTypes` as a static property of the class:
189-
190-
```js
191-
class MyComponent extends React.Component {
192-
render() {
193-
// Here, you can use this.context.router.
194-
}
195-
}
196-
197-
MyComponent.contextTypes = {
198-
router: routerShape.isRequired
199-
}
200-
```
201-
202-
If you are using the class properties proposal, you can instead write:
203-
204-
```js
205-
class MyComponent extends React.Component {
206-
static contextTypes = {
207-
router: routerShape.isRequired
208-
}
209-
210-
render() {
211-
// Here, you can use this.context.router.
212-
}
213-
}
214-
```
215-
216-
To use `context.router` with
217-
[stateless function components](https://facebook.github.io/react/docs/reusable-components.html#stateless-functions), declare `contextTypes` as a static property of the component function:
218-
219-
```js
220-
function MyComponent(props, context) {
221-
// Here, you can use context.router.
222-
}
223-
224-
MyComponent.contextTypes = {
225-
router: routerShape.isRequired
226-
}
227-
```
228-
229178
##### `push(pathOrLoc)`
230179
Transitions to a new URL, adding a new entry in the browser history.
231180

docs/Troubleshooting.md

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,17 @@
11
# Troubleshooting
22

3-
### `this.context.router` is `undefined`
3+
### How do I add `this.props.router` to my component?
44

5-
You need to add `router` to your component's `contextTypes` to make the router object available to you.
5+
You need to wrap your component using `withRouter` to make the router object available to you.
66

77
```js
8-
contextTypes: {
9-
router: routerShape.isRequired
10-
}
8+
const Component = withRouter(
9+
React.createClass({
10+
//...
11+
})
12+
)
1113
```
1214

13-
1415
### Getting the previous location
1516

1617
```js

docs/guides/ConfirmingNavigation.md

Lines changed: 14 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -3,24 +3,22 @@
33
You can prevent a transition from happening or prompt the user before leaving a [route](/docs/Glossary.md#route) with a leave hook.
44

55
```js
6-
const Home = React.createClass({
6+
const Home = withRouter(
7+
React.createClass({
78

8-
contextTypes: {
9-
router: routerShape.isRequired
10-
},
9+
componentDidMount() {
10+
this.props.router.setRouteLeaveHook(this.props.route, this.routerWillLeave)
11+
},
1112

12-
componentDidMount() {
13-
this.context.router.setRouteLeaveHook(this.props.route, this.routerWillLeave)
14-
},
13+
routerWillLeave(nextLocation) {
14+
// return false to prevent a transition w/o prompting the user,
15+
// or return a string to allow the user to decide:
16+
if (!this.state.isSaved)
17+
return 'Your work is not saved! Are you sure you want to leave?'
18+
},
1519

16-
routerWillLeave(nextLocation) {
17-
// return false to prevent a transition w/o prompting the user,
18-
// or return a string to allow the user to decide:
19-
if (!this.state.isSaved)
20-
return 'Your work is not saved! Are you sure you want to leave?'
21-
},
20+
// ...
2221

23-
// ...
24-
25-
})
22+
})
23+
)
2624
```

docs/guides/NavigatingOutsideOfComponents.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# Navigating Outside of Components
22

3-
While you can use `this.context.router` to navigate around, many apps want to be able to navigate outside of their components. They can do that with the history the app gives to `Router`.
3+
While you can use `this.props.router` from `withRouter` to navigate around, many apps want to be able to navigate outside of their components. They can do that with the history the app gives to `Router`.
44

55
```js
66
// your main file that renders a Router

examples/auth-flow-async-with-query-params/app.js

Lines changed: 33 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import React, { createClass } from 'react'
22
import { render } from 'react-dom'
33
import {
4-
Router, Route, IndexRoute, browserHistory, Link, routerShape
4+
Router, Route, IndexRoute, browserHistory, Link, withRouter
55
} from 'react-router'
66

77
function App(props) {
@@ -12,43 +12,42 @@ function App(props) {
1212
)
1313
}
1414

15-
const Form = createClass({
16-
contextTypes: {
17-
router: routerShape.isRequired
18-
},
15+
const Form = withRouter(
16+
createClass({
1917

20-
getInitialState() {
21-
return {
22-
value: ''
23-
}
24-
},
25-
26-
submitAction(event) {
27-
event.preventDefault()
28-
this.context.router.push({
29-
pathname: '/page',
30-
query: {
31-
qsparam: this.state.value
18+
getInitialState() {
19+
return {
20+
value: ''
3221
}
33-
})
34-
},
22+
},
3523

36-
handleChange(event) {
37-
this.setState({ value: event.target.value })
38-
},
24+
submitAction(event) {
25+
event.preventDefault()
26+
this.props.router.push({
27+
pathname: '/page',
28+
query: {
29+
qsparam: this.state.value
30+
}
31+
})
32+
},
3933

40-
render() {
41-
return (
42-
<form onSubmit={this.submitAction}>
43-
<p>Token is <em>pancakes</em></p>
44-
<input type="text" value={this.state.value} onChange={this.handleChange} />
45-
<button type="submit">Submit the thing</button>
46-
<p><Link to="/page?qsparam=pancakes">Or authenticate via URL</Link></p>
47-
<p><Link to="/page?qsparam=bacon">Or try failing to authenticate via URL</Link></p>
48-
</form>
49-
)
50-
}
51-
})
34+
handleChange(event) {
35+
this.setState({ value: event.target.value })
36+
},
37+
38+
render() {
39+
return (
40+
<form onSubmit={this.submitAction}>
41+
<p>Token is <em>pancakes</em></p>
42+
<input type="text" value={this.state.value} onChange={this.handleChange} />
43+
<button type="submit">Submit the thing</button>
44+
<p><Link to="/page?qsparam=pancakes">Or authenticate via URL</Link></p>
45+
<p><Link to="/page?qsparam=bacon">Or try failing to authenticate via URL</Link></p>
46+
</form>
47+
)
48+
}
49+
})
50+
)
5251

5352
function Page() {
5453
return <h1>Hey, I see you are authenticated. Welcome!</h1>

examples/auth-flow/app.js

Lines changed: 42 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import React from 'react'
22
import { render } from 'react-dom'
3-
import { browserHistory, Router, Route, Link, routerShape } from 'react-router'
3+
import { browserHistory, Router, Route, Link, withRouter } from 'react-router'
44
import auth from './auth'
55

66
const App = React.createClass({
@@ -55,51 +55,49 @@ const Dashboard = React.createClass({
5555
}
5656
})
5757

58-
const Login = React.createClass({
58+
const Login = withRouter(
59+
React.createClass({
5960

60-
contextTypes: {
61-
router: routerShape.isRequired
62-
},
63-
64-
getInitialState() {
65-
return {
66-
error: false
67-
}
68-
},
69-
70-
handleSubmit(event) {
71-
event.preventDefault()
72-
73-
const email = this.refs.email.value
74-
const pass = this.refs.pass.value
75-
76-
auth.login(email, pass, (loggedIn) => {
77-
if (!loggedIn)
78-
return this.setState({ error: true })
79-
80-
const { location } = this.props
81-
82-
if (location.state && location.state.nextPathname) {
83-
this.context.router.replace(location.state.nextPathname)
84-
} else {
85-
this.context.router.replace('/')
61+
getInitialState() {
62+
return {
63+
error: false
8664
}
87-
})
88-
},
89-
90-
render() {
91-
return (
92-
<form onSubmit={this.handleSubmit}>
93-
<label><input ref="email" placeholder="email" defaultValue="joe@example.com" /></label>
94-
<label><input ref="pass" placeholder="password" /></label> (hint: password1)<br />
95-
<button type="submit">login</button>
96-
{this.state.error && (
97-
<p>Bad login information</p>
98-
)}
99-
</form>
100-
)
101-
}
102-
})
65+
},
66+
67+
handleSubmit(event) {
68+
event.preventDefault()
69+
70+
const email = this.refs.email.value
71+
const pass = this.refs.pass.value
72+
73+
auth.login(email, pass, (loggedIn) => {
74+
if (!loggedIn)
75+
return this.setState({ error: true })
76+
77+
const { location } = this.props
78+
79+
if (location.state && location.state.nextPathname) {
80+
this.props.router.replace(location.state.nextPathname)
81+
} else {
82+
this.props.router.replace('/')
83+
}
84+
})
85+
},
86+
87+
render() {
88+
return (
89+
<form onSubmit={this.handleSubmit}>
90+
<label><input ref="email" placeholder="email" defaultValue="joe@example.com" /></label>
91+
<label><input ref="pass" placeholder="password" /></label> (hint: password1)<br />
92+
<button type="submit">login</button>
93+
{this.state.error && (
94+
<p>Bad login information</p>
95+
)}
96+
</form>
97+
)
98+
}
99+
})
100+
)
103101

104102
const About = React.createClass({
105103
render() {

examples/auth-with-shared-root/components/Login.js

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,8 @@
11
import React from 'react'
2+
import { withRouter } from 'react-router'
23
import auth from '../utils/auth.js'
34

45
const Login = React.createClass({
5-
6-
contextTypes: {
7-
router: React.PropTypes.object
8-
},
9-
106
getInitialState() {
117
return {
128
error: false
@@ -26,9 +22,9 @@ const Login = React.createClass({
2622
const { location } = this.props
2723

2824
if (location.state && location.state.nextPathname) {
29-
this.context.router.replace(location.state.nextPathname)
25+
this.props.router.replace(location.state.nextPathname)
3026
} else {
31-
this.context.router.replace('/')
27+
this.props.router.replace('/')
3228
}
3329
})
3430
},
@@ -48,4 +44,4 @@ const Login = React.createClass({
4844

4945
})
5046

51-
export default Login
47+
export default withRouter(Login)

0 commit comments

Comments
 (0)