From a7b0ffded88bd83ce5970861795073593e226fa3 Mon Sep 17 00:00:00 2001 From: Tim Dorr Date: Mon, 18 Apr 2016 17:00:47 -0400 Subject: [PATCH 1/5] Create a withRouter HoC. --- modules/__tests__/withRouter-test.js | 40 ++++++++++++++++++++++++++++ modules/withRouter.js | 12 +++++++++ package.json | 1 + 3 files changed, 53 insertions(+) create mode 100644 modules/__tests__/withRouter-test.js create mode 100644 modules/withRouter.js diff --git a/modules/__tests__/withRouter-test.js b/modules/__tests__/withRouter-test.js new file mode 100644 index 0000000000..5029e5a2c5 --- /dev/null +++ b/modules/__tests__/withRouter-test.js @@ -0,0 +1,40 @@ +import expect from 'expect' +import React, { Component } from 'react' +import { render, unmountComponentAtNode } from 'react-dom' +import createHistory from '../createMemoryHistory' +import Route from '../Route' +import Router from '../Router' +import withRouter from '../withRouter' +import resetHash from './resetHash' + +describe('withRouter', function () { + class App extends Component { + render() { + expect(this.context.router).toExist() + return

App

+ } + } + + beforeEach(resetHash) + + let node + beforeEach(function () { + node = document.createElement('div') + }) + + afterEach(function () { + unmountComponentAtNode(node) + }) + + it('puts router on context', function (done) { + const WrappedApp = withRouter(App) + + render(( + + + + ), node, function () { + done() + }) + }) +}) diff --git a/modules/withRouter.js b/modules/withRouter.js new file mode 100644 index 0000000000..01af831429 --- /dev/null +++ b/modules/withRouter.js @@ -0,0 +1,12 @@ +import hoistStatics from 'hoist-non-react-statics' +import { routerShape } from './PropTypes' + +export default function withRouter(Component) { + const WithRouter = class extends Component {} + + WithRouter.contextTypes = { + router: routerShape + } + + return hoistStatics(WithRouter, Component) +} diff --git a/package.json b/package.json index 43dcf1cc14..40bc492a1d 100644 --- a/package.json +++ b/package.json @@ -33,6 +33,7 @@ "license": "MIT", "dependencies": { "history": "^2.0.1", + "hoist-non-react-statics": "^1.0.5", "invariant": "^2.2.1", "warning": "^2.1.0" }, From 4c3f3e7eb317222576ddb137d91813c88f4f46c6 Mon Sep 17 00:00:00 2001 From: Tim Dorr Date: Mon, 18 Apr 2016 18:09:10 -0400 Subject: [PATCH 2/5] withRouter passes this.props.router --- modules/__tests__/withRouter-test.js | 9 +++++---- modules/withRouter.js | 21 +++++++++++++++------ 2 files changed, 20 insertions(+), 10 deletions(-) diff --git a/modules/__tests__/withRouter-test.js b/modules/__tests__/withRouter-test.js index 5029e5a2c5..ebfdc58fbd 100644 --- a/modules/__tests__/withRouter-test.js +++ b/modules/__tests__/withRouter-test.js @@ -4,19 +4,20 @@ import { render, unmountComponentAtNode } from 'react-dom' import createHistory from '../createMemoryHistory' import Route from '../Route' import Router from '../Router' +import routerShape from '../PropTypes' import withRouter from '../withRouter' -import resetHash from './resetHash' describe('withRouter', function () { class App extends Component { + propTypes: { + router: routerShape.isRequired + } render() { - expect(this.context.router).toExist() + expect(this.props.router).toExist() return

App

} } - beforeEach(resetHash) - let node beforeEach(function () { node = document.createElement('div') diff --git a/modules/withRouter.js b/modules/withRouter.js index 01af831429..e9e7f32fda 100644 --- a/modules/withRouter.js +++ b/modules/withRouter.js @@ -1,12 +1,21 @@ +import React from 'react' import hoistStatics from 'hoist-non-react-statics' import { routerShape } from './PropTypes' -export default function withRouter(Component) { - const WithRouter = class extends Component {} +function getDisplayName(WrappedComponent) { + return WrappedComponent.displayName || WrappedComponent.name || 'Component' +} + +export default function withRouter(WrappedComponent) { + const WithRouter = React.createClass({ + contextTypes: { router: routerShape }, + render() { + return + } + }) - WithRouter.contextTypes = { - router: routerShape - } + WithRouter.displayName = `withRouter(${getDisplayName(WrappedComponent)})` + WithRouter.WrappedComponent = WrappedComponent - return hoistStatics(WithRouter, Component) + return hoistStatics(WithRouter, WrappedComponent) } From d21de1e72af8e1d17502cc46805afb2e60b4b496 Mon Sep 17 00:00:00 2001 From: Tim Dorr Date: Tue, 19 Apr 2016 23:21:07 -0400 Subject: [PATCH 3/5] Update docs, examples, and upgrade guide. Also add an export of withRouter --- docs/API.md | 59 +------ docs/Troubleshooting.md | 12 +- docs/guides/ConfirmingNavigation.md | 32 ++-- docs/guides/NavigatingOutsideOfComponents.md | 2 +- .../auth-flow-async-with-query-params/app.js | 67 ++++---- examples/auth-flow/app.js | 86 +++++----- .../auth-with-shared-root/components/Login.js | 12 +- examples/confirming-navigation/app.js | 91 ++++++----- examples/master-detail/app.js | 154 +++++++++--------- examples/passing-props-to-children/app.js | 97 +++++------ modules/index.js | 1 + upgrade-guides/v2.0.0.md | 4 +- upgrade-guides/v2.4.0.md | 33 ++++ 13 files changed, 321 insertions(+), 329 deletions(-) create mode 100644 upgrade-guides/v2.4.0.md diff --git a/docs/API.md b/docs/API.md index 5f85df59b8..a8221f436b 100644 --- a/docs/API.md +++ b/docs/API.md @@ -4,6 +4,7 @@ - [``](#router) - [``](#link) - [``](#indexlink) + - [`withRouter`](#withRouter-component) - [``](#routercontext) - [`context.router`](#contextrouter) - `` (deprecated, use ``) @@ -162,6 +163,9 @@ Given a route like ``: ### `` An `` is like a [``](#link), except it is only active when the current route is exactly the linked route. It is equivalent to `` with the `onlyActiveOnIndex` prop set. +### `withRouter(component)` +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. + ### `` A `` renders the component tree for a given router state. Its used by `` but also useful for server rendering and integrating in brownfield development. @@ -171,61 +175,6 @@ It also provides a `router` object on [context](https://facebook.github.io/react Contains data and methods relevant to routing. Most useful for imperatively transitioning around the application. -To use it, you must signal to React that you need it by declaring your use of it in your component via `contextTypes`: - -```js -var MyComponent = React.createClass({ - contextTypes: { - router: routerShape.isRequired - }, - - render: function() { - // Here, you can use this.context.router. - } -}) -``` - -To use `context.router` on a component declared as an ES2015 class, define `contextTypes` as a static property of the class: - -```js -class MyComponent extends React.Component { - render() { - // Here, you can use this.context.router. - } -} - -MyComponent.contextTypes = { - router: routerShape.isRequired -} -``` - -If you are using the class properties proposal, you can instead write: - -```js -class MyComponent extends React.Component { - static contextTypes = { - router: routerShape.isRequired - } - - render() { - // Here, you can use this.context.router. - } -} -``` - -To use `context.router` with -[stateless function components](https://facebook.github.io/react/docs/reusable-components.html#stateless-functions), declare `contextTypes` as a static property of the component function: - -```js -function MyComponent(props, context) { - // Here, you can use context.router. -} - -MyComponent.contextTypes = { - router: routerShape.isRequired -} -``` - ##### `push(pathOrLoc)` Transitions to a new URL, adding a new entry in the browser history. diff --git a/docs/Troubleshooting.md b/docs/Troubleshooting.md index 016c17fc8f..41a2e6f3b3 100644 --- a/docs/Troubleshooting.md +++ b/docs/Troubleshooting.md @@ -1,13 +1,15 @@ # Troubleshooting -### `this.context.router` is `undefined` +### `this.props.router` is `undefined` -You need to add `router` to your component's `contextTypes` to make the router object available to you. +You need to wrap your component using `withRouter` to make the router object available to you. ```js -contextTypes: { - router: routerShape.isRequired -} +const Component = withRouter( + React.createClass({ + //... + }) +) ``` diff --git a/docs/guides/ConfirmingNavigation.md b/docs/guides/ConfirmingNavigation.md index 5561696024..4531df643a 100644 --- a/docs/guides/ConfirmingNavigation.md +++ b/docs/guides/ConfirmingNavigation.md @@ -3,24 +3,26 @@ You can prevent a transition from happening or prompt the user before leaving a [route](/docs/Glossary.md#route) with a leave hook. ```js -const Home = React.createClass({ +const Home = withRouter( + React.createClass({ - contextTypes: { - router: routerShape.isRequired - }, + contextTypes: { + router: routerShape.isRequired + }, - componentDidMount() { - this.context.router.setRouteLeaveHook(this.props.route, this.routerWillLeave) - }, + componentDidMount() { + this.props.router.setRouteLeaveHook(this.props.route, this.routerWillLeave) + }, - routerWillLeave(nextLocation) { - // return false to prevent a transition w/o prompting the user, - // or return a string to allow the user to decide: - if (!this.state.isSaved) - return 'Your work is not saved! Are you sure you want to leave?' - }, + routerWillLeave(nextLocation) { + // return false to prevent a transition w/o prompting the user, + // or return a string to allow the user to decide: + if (!this.state.isSaved) + return 'Your work is not saved! Are you sure you want to leave?' + }, - // ... + // ... -}) + }) +) ``` diff --git a/docs/guides/NavigatingOutsideOfComponents.md b/docs/guides/NavigatingOutsideOfComponents.md index c29ba056f4..a2233b3f7e 100644 --- a/docs/guides/NavigatingOutsideOfComponents.md +++ b/docs/guides/NavigatingOutsideOfComponents.md @@ -1,6 +1,6 @@ # Navigating Outside of Components -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`. +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`. ```js // your main file that renders a Router diff --git a/examples/auth-flow-async-with-query-params/app.js b/examples/auth-flow-async-with-query-params/app.js index 4877bcab7d..ac395e13e8 100644 --- a/examples/auth-flow-async-with-query-params/app.js +++ b/examples/auth-flow-async-with-query-params/app.js @@ -1,7 +1,7 @@ import React, { createClass } from 'react' import { render } from 'react-dom' import { - Router, Route, IndexRoute, browserHistory, Link, routerShape + Router, Route, IndexRoute, browserHistory, Link, withRouter } from 'react-router' function App(props) { @@ -12,43 +12,44 @@ function App(props) { ) } -const Form = createClass({ - contextTypes: { - router: routerShape.isRequired - }, +const Form = withRouter( + createClass({ - getInitialState() { - return { - value: '' - } - }, + displayName: 'Form', - submitAction(event) { - event.preventDefault() - this.context.router.push({ - pathname: '/page', - query: { - qsparam: this.state.value + getInitialState() { + return { + value: '' } - }) - }, + }, - handleChange(event) { - this.setState({ value: event.target.value }) - }, + submitAction(event) { + event.preventDefault() + this.props.router.push({ + pathname: '/page', + query: { + qsparam: this.state.value + } + }) + }, - render() { - return ( -
-

Token is pancakes

- - -

Or authenticate via URL

-

Or try failing to authenticate via URL

-
- ) - } -}) + handleChange(event) { + this.setState({ value: event.target.value }) + }, + + render() { + return ( +
+

Token is pancakes

+ + +

Or authenticate via URL

+

Or try failing to authenticate via URL

+
+ ) + } + }) +) function Page() { return

Hey, I see you are authenticated. Welcome!

diff --git a/examples/auth-flow/app.js b/examples/auth-flow/app.js index 6df7a6c242..8ace5236f2 100644 --- a/examples/auth-flow/app.js +++ b/examples/auth-flow/app.js @@ -1,6 +1,6 @@ import React from 'react' import { render } from 'react-dom' -import { browserHistory, Router, Route, Link, routerShape } from 'react-router' +import { browserHistory, Router, Route, Link, withRouter } from 'react-router' import auth from './auth' const App = React.createClass({ @@ -55,51 +55,51 @@ const Dashboard = React.createClass({ } }) -const Login = React.createClass({ +const Login = withRouter( + React.createClass({ - contextTypes: { - router: routerShape.isRequired - }, - - getInitialState() { - return { - error: false - } - }, - - handleSubmit(event) { - event.preventDefault() - - const email = this.refs.email.value - const pass = this.refs.pass.value - - auth.login(email, pass, (loggedIn) => { - if (!loggedIn) - return this.setState({ error: true }) - - const { location } = this.props + displayName: 'Login', - if (location.state && location.state.nextPathname) { - this.context.router.replace(location.state.nextPathname) - } else { - this.context.router.replace('/') + getInitialState() { + return { + error: false } - }) - }, - - render() { - return ( -
- - (hint: password1)
- - {this.state.error && ( -

Bad login information

- )} -
- ) - } -}) + }, + + handleSubmit(event) { + event.preventDefault() + + const email = this.refs.email.value + const pass = this.refs.pass.value + + auth.login(email, pass, (loggedIn) => { + if (!loggedIn) + return this.setState({ error: true }) + + const { location } = this.props + + if (location.state && location.state.nextPathname) { + this.props.router.replace(location.state.nextPathname) + } else { + this.props.router.replace('/') + } + }) + }, + + render() { + return ( +
+ + (hint: password1)
+ + {this.state.error && ( +

Bad login information

+ )} +
+ ) + } + }) +) const About = React.createClass({ render() { diff --git a/examples/auth-with-shared-root/components/Login.js b/examples/auth-with-shared-root/components/Login.js index 6500df5a89..c433c26a04 100644 --- a/examples/auth-with-shared-root/components/Login.js +++ b/examples/auth-with-shared-root/components/Login.js @@ -1,11 +1,9 @@ import React from 'react' +import { withRouter } from 'react-router' import auth from '../utils/auth.js' const Login = React.createClass({ - - contextTypes: { - router: React.PropTypes.object - }, + displayName: 'Login', getInitialState() { return { @@ -26,9 +24,9 @@ const Login = React.createClass({ const { location } = this.props if (location.state && location.state.nextPathname) { - this.context.router.replace(location.state.nextPathname) + this.props.router.replace(location.state.nextPathname) } else { - this.context.router.replace('/') + this.props.router.replace('/') } }) }, @@ -48,4 +46,4 @@ const Login = React.createClass({ }) -export default Login +export default withRouter(Login) diff --git a/examples/confirming-navigation/app.js b/examples/confirming-navigation/app.js index ad32a3e780..c652899f2f 100644 --- a/examples/confirming-navigation/app.js +++ b/examples/confirming-navigation/app.js @@ -1,6 +1,6 @@ import React from 'react' import { render } from 'react-dom' -import { browserHistory, Router, Route, Link, routerShape } from 'react-router' +import { browserHistory, Router, Route, Link, withRouter } from 'react-router' const App = React.createClass({ render() { @@ -22,57 +22,58 @@ const Dashboard = React.createClass({ } }) -const Form = React.createClass({ - contextTypes: { - router: routerShape.isRequired - }, +const Form = withRouter( + React.createClass({ - componentWillMount() { - this.context.router.setRouteLeaveHook( - this.props.route, - this.routerWillLeave - ) - }, + displayName: 'Form', - getInitialState() { - return { - textValue: 'ohai' - } - }, + componentWillMount() { + this.props.router.setRouteLeaveHook( + this.props.route, + this.routerWillLeave + ) + }, - routerWillLeave() { - if (this.state.textValue) - return 'You have unsaved information, are you sure you want to leave this page?' - }, + getInitialState() { + return { + textValue: 'ohai' + } + }, - handleChange(event) { - this.setState({ - textValue: event.target.value - }) - }, + routerWillLeave() { + if (this.state.textValue) + return 'You have unsaved information, are you sure you want to leave this page?' + }, - handleSubmit(event) { - event.preventDefault() + handleChange(event) { + this.setState({ + textValue: event.target.value + }) + }, - this.setState({ - textValue: '' - }, () => { - this.context.router.push('/') - }) - }, + handleSubmit(event) { + event.preventDefault() - render() { - return ( -
-
-

Click the dashboard link with text in the input.

- - -
-
- ) - } -}) + this.setState({ + textValue: '' + }, () => { + this.props.router.push('/') + }) + }, + + render() { + return ( +
+
+

Click the dashboard link with text in the input.

+ + +
+
+ ) + } + }) +) render(( diff --git a/examples/master-detail/app.js b/examples/master-detail/app.js index 6123378314..2b28deb2ba 100644 --- a/examples/master-detail/app.js +++ b/examples/master-detail/app.js @@ -1,7 +1,7 @@ import React from 'react' import { render, findDOMNode } from 'react-dom' import { - browserHistory, Router, Route, IndexRoute, Link, routerShape + browserHistory, Router, Route, IndexRoute, Link, withRouter } from 'react-router' import ContactStore from './ContactStore' import './app.css' @@ -63,93 +63,95 @@ const Index = React.createClass({ } }) -const Contact = React.createClass({ - contextTypes: { - router: routerShape.isRequired - }, +const Contact = withRouter( + React.createClass({ - getStateFromStore(props) { - const { id } = props ? props.params : this.props.params + displayName: 'Contact', - return { - contact: ContactStore.getContact(id) - } - }, + getStateFromStore(props) { + const { id } = props ? props.params : this.props.params - getInitialState() { - return this.getStateFromStore() - }, + return { + contact: ContactStore.getContact(id) + } + }, - componentDidMount() { - ContactStore.addChangeListener(this.updateContact) - }, + getInitialState() { + return this.getStateFromStore() + }, - componentWillUnmount() { - ContactStore.removeChangeListener(this.updateContact) - }, + componentDidMount() { + ContactStore.addChangeListener(this.updateContact) + }, - componentWillReceiveProps(nextProps) { - this.setState(this.getStateFromStore(nextProps)) - }, + componentWillUnmount() { + ContactStore.removeChangeListener(this.updateContact) + }, - updateContact() { - if (!this.isMounted()) - return + componentWillReceiveProps(nextProps) { + this.setState(this.getStateFromStore(nextProps)) + }, - this.setState(this.getStateFromStore()) - }, + updateContact() { + if (!this.isMounted()) + return - destroy() { - const { id } = this.props.params - ContactStore.removeContact(id) - this.context.router.push('/') - }, + this.setState(this.getStateFromStore()) + }, - render() { - const contact = this.state.contact || {} - const name = contact.first + ' ' + contact.last - const avatar = contact.avatar || 'http://placecage.com/50/50' + destroy() { + const { id } = this.props.params + ContactStore.removeContact(id) + this.props.router.push('/') + }, - return ( -
- -

{name}

- -
- ) - } -}) - -const NewContact = React.createClass({ - contextTypes: { - router: routerShape.isRequired - }, - - createContact(event) { - event.preventDefault() - - ContactStore.addContact({ - first: findDOMNode(this.refs.first).value, - last: findDOMNode(this.refs.last).value - }, (contact) => { - this.context.router.push(`/contact/${contact.id}`) - }) - }, + render() { + const contact = this.state.contact || {} + const name = contact.first + ' ' + contact.last + const avatar = contact.avatar || 'http://placecage.com/50/50' - render() { - return ( -
-

- - -

-

- Cancel -

-
- ) - } -}) + return ( +
+ +

{name}

+ +
+ ) + } + }) +) + +const NewContact = withRouter( + React.createClass({ + + displayName: 'NewContact', + + createContact(event) { + event.preventDefault() + + ContactStore.addContact({ + first: findDOMNode(this.refs.first).value, + last: findDOMNode(this.refs.last).value + }, (contact) => { + this.props.router.push(`/contact/${contact.id}`) + }) + }, + + render() { + return ( +
+

+ + +

+

+ Cancel +

+
+ ) + } + }) +) const NotFound = React.createClass({ render() { diff --git a/examples/passing-props-to-children/app.js b/examples/passing-props-to-children/app.js index ba98d42501..b514d54a10 100644 --- a/examples/passing-props-to-children/app.js +++ b/examples/passing-props-to-children/app.js @@ -1,64 +1,65 @@ import React from 'react' import { render } from 'react-dom' -import { browserHistory, Router, Route, Link, routerShape } from 'react-router' +import { browserHistory, Router, Route, Link, withRouter } from 'react-router' import './app.css' -const App = React.createClass({ - contextTypes: { - router: routerShape.isRequired - }, +const App = withRouter( + React.createClass({ - getInitialState() { - return { - tacos: [ - { name: 'duck confit' }, - { name: 'carne asada' }, - { name: 'shrimp' } - ] - } - }, + displayName: 'App', - addTaco() { - let name = prompt('taco name?') + getInitialState() { + return { + tacos: [ + { name: 'duck confit' }, + { name: 'carne asada' }, + { name: 'shrimp' } + ] + } + }, - this.setState({ - tacos: this.state.tacos.concat({ name }) - }) - }, + addTaco() { + let name = prompt('taco name?') - handleRemoveTaco(removedTaco) { - this.setState({ - tacos: this.state.tacos.filter(function (taco) { - return taco.name != removedTaco + this.setState({ + tacos: this.state.tacos.concat({ name }) }) - }) + }, - this.context.router.push('/') - }, + handleRemoveTaco(removedTaco) { + this.setState({ + tacos: this.state.tacos.filter(function (taco) { + return taco.name != removedTaco + }) + }) - render() { - let links = this.state.tacos.map(function (taco, i) { + this.props.router.push('/') + }, + + render() { + let links = this.state.tacos.map(function (taco, i) { + return ( +
  • + {taco.name} +
  • + ) + }) return ( -
  • - {taco.name} -
  • - ) - }) - return ( -
    - -
      - {links} -
    -
    - {this.props.children && React.cloneElement(this.props.children, { - onRemoveTaco: this.handleRemoveTaco - })} +
    + +
      + {links} +
    +
    + {this.props.children && React.cloneElement(this.props.children, { + onRemoveTaco: this.handleRemoveTaco + })} +
    -
    - ) - } -}) + ) + } + }) +) const Taco = React.createClass({ remove() { diff --git a/modules/index.js b/modules/index.js index 56761bad18..4317161557 100644 --- a/modules/index.js +++ b/modules/index.js @@ -2,6 +2,7 @@ export Router from './Router' export Link from './Link' export IndexLink from './IndexLink' +export withRouter from './withRouter' /* components (configuration) */ export IndexRedirect from './IndexRedirect' diff --git a/upgrade-guides/v2.0.0.md b/upgrade-guides/v2.0.0.md index b93bc7193e..324ea0624f 100644 --- a/upgrade-guides/v2.0.0.md +++ b/upgrade-guides/v2.0.0.md @@ -102,7 +102,9 @@ const appHistory = useScroll(useRouterHistory(createBrowserHistory))(); ## Changes to `this.context` -Only an object named `router` is added to context. Accessing `this.context.history`, `this.context.location`, and `this.context.route` are all deprecated. This new object contains the methods available from `history` (such as `push`, `replace`) along with `setRouteLeaveHook`. +**NOTE: v2.4.0 and higher include a higher-order component `withRouter` that is now the preferred way of accessing the `router` object.** Read [the v2.4.0 upgrade guide](/upgrade-guides/v2.4.0.md) for more details. + +Only an object named `router` is added to context. Accessing `this.context.history`, `this.context.location`, and `this.context.route` are all deprecated. This new object contains the methods available from `history` (such as `push`, `replace`) along with `setRouteLeaveHook` and `isActive`. ### Accessing location diff --git a/upgrade-guides/v2.4.0.md b/upgrade-guides/v2.4.0.md new file mode 100644 index 0000000000..7959d1bd46 --- /dev/null +++ b/upgrade-guides/v2.4.0.md @@ -0,0 +1,33 @@ +# v2.4.0 Upgrade Guide + +## `withRouter` HoC (higher-order component) + +Prior to 2.4.0, you could access the `router` object via [`this.context`](https://facebook.github.io/react/docs/context.html). This is still true, but `context` is often times a difficult and error-prone API to work with. + +In order to more easily access the `router` object, a `withRouter` higher-order component has been added as the new primary means of access. As with other HoCs, it is usable on any React Component of any type (`React.createClass`, ES2015 `React.Component` classes, stateless functional components). + +```js +import React from 'react' +import { withRouter } from 'react-router' + +const Page = React.createClass({ + + displayName: 'Page', + + componentDidMount() { + this.props.router.setRouteLeaveHook(this.props.route, () => { + if (this.state.unsaved) + return 'You have unsaved information, are you sure you want to leave this page?' + }) + } + + render() { + return
    Stuff
    + } + +}) + +export default withRouter(Login) +``` + +**It's important to note this is not a deprecation of the `context` API.** As long as React supports `this.context` in its current form, any code written for that API will continue to work. We will continue to use it internally and you can continue to write in that format, if you want. We think this new HoC is nicer and easier, and will be using it in documentation and examples, but it is not a hard requirement to switch. From 344c86a9861c14e113e1e0604b7a5ecb0e330d55 Mon Sep 17 00:00:00 2001 From: Tim Dorr Date: Tue, 19 Apr 2016 23:37:52 -0400 Subject: [PATCH 4/5] Doc tweaks. --- docs/Troubleshooting.md | 3 +-- docs/guides/ConfirmingNavigation.md | 4 ---- upgrade-guides/v2.0.0.md | 2 +- 3 files changed, 2 insertions(+), 7 deletions(-) diff --git a/docs/Troubleshooting.md b/docs/Troubleshooting.md index 41a2e6f3b3..231c6670aa 100644 --- a/docs/Troubleshooting.md +++ b/docs/Troubleshooting.md @@ -1,6 +1,6 @@ # Troubleshooting -### `this.props.router` is `undefined` +### How do I add `this.props.router` to my component? You need to wrap your component using `withRouter` to make the router object available to you. @@ -12,7 +12,6 @@ const Component = withRouter( ) ``` - ### Getting the previous location ```js diff --git a/docs/guides/ConfirmingNavigation.md b/docs/guides/ConfirmingNavigation.md index 4531df643a..bedcb6fc41 100644 --- a/docs/guides/ConfirmingNavigation.md +++ b/docs/guides/ConfirmingNavigation.md @@ -6,10 +6,6 @@ You can prevent a transition from happening or prompt the user before leaving a const Home = withRouter( React.createClass({ - contextTypes: { - router: routerShape.isRequired - }, - componentDidMount() { this.props.router.setRouteLeaveHook(this.props.route, this.routerWillLeave) }, diff --git a/upgrade-guides/v2.0.0.md b/upgrade-guides/v2.0.0.md index 324ea0624f..cf7786b6d3 100644 --- a/upgrade-guides/v2.0.0.md +++ b/upgrade-guides/v2.0.0.md @@ -102,7 +102,7 @@ const appHistory = useScroll(useRouterHistory(createBrowserHistory))(); ## Changes to `this.context` -**NOTE: v2.4.0 and higher include a higher-order component `withRouter` that is now the preferred way of accessing the `router` object.** Read [the v2.4.0 upgrade guide](/upgrade-guides/v2.4.0.md) for more details. +**NOTE: v2.4.0 and higher include a higher-order component `withRouter` that is now the (highly) recommended way of accessing the `router` object.** Read [the v2.4.0 upgrade guide](/upgrade-guides/v2.4.0.md) for more details. Only an object named `router` is added to context. Accessing `this.context.history`, `this.context.location`, and `this.context.route` are all deprecated. This new object contains the methods available from `history` (such as `push`, `replace`) along with `setRouteLeaveHook` and `isActive`. From 61a3c37a4a8a15e60c23bb0cea6e2b59dedf5d3c Mon Sep 17 00:00:00 2001 From: Tim Dorr Date: Thu, 21 Apr 2016 03:29:41 -0400 Subject: [PATCH 5/5] Remove displayNames. --- examples/auth-flow-async-with-query-params/app.js | 2 -- examples/auth-flow/app.js | 2 -- examples/auth-with-shared-root/components/Login.js | 2 -- examples/confirming-navigation/app.js | 2 -- examples/master-detail/app.js | 4 ---- examples/passing-props-to-children/app.js | 2 -- upgrade-guides/v2.4.0.md | 2 -- 7 files changed, 16 deletions(-) diff --git a/examples/auth-flow-async-with-query-params/app.js b/examples/auth-flow-async-with-query-params/app.js index ac395e13e8..3475f2ea1e 100644 --- a/examples/auth-flow-async-with-query-params/app.js +++ b/examples/auth-flow-async-with-query-params/app.js @@ -15,8 +15,6 @@ function App(props) { const Form = withRouter( createClass({ - displayName: 'Form', - getInitialState() { return { value: '' diff --git a/examples/auth-flow/app.js b/examples/auth-flow/app.js index 8ace5236f2..54f4ed3ad2 100644 --- a/examples/auth-flow/app.js +++ b/examples/auth-flow/app.js @@ -58,8 +58,6 @@ const Dashboard = React.createClass({ const Login = withRouter( React.createClass({ - displayName: 'Login', - getInitialState() { return { error: false diff --git a/examples/auth-with-shared-root/components/Login.js b/examples/auth-with-shared-root/components/Login.js index c433c26a04..6770900065 100644 --- a/examples/auth-with-shared-root/components/Login.js +++ b/examples/auth-with-shared-root/components/Login.js @@ -3,8 +3,6 @@ import { withRouter } from 'react-router' import auth from '../utils/auth.js' const Login = React.createClass({ - displayName: 'Login', - getInitialState() { return { error: false diff --git a/examples/confirming-navigation/app.js b/examples/confirming-navigation/app.js index c652899f2f..5edb688003 100644 --- a/examples/confirming-navigation/app.js +++ b/examples/confirming-navigation/app.js @@ -25,8 +25,6 @@ const Dashboard = React.createClass({ const Form = withRouter( React.createClass({ - displayName: 'Form', - componentWillMount() { this.props.router.setRouteLeaveHook( this.props.route, diff --git a/examples/master-detail/app.js b/examples/master-detail/app.js index 2b28deb2ba..8b445f5476 100644 --- a/examples/master-detail/app.js +++ b/examples/master-detail/app.js @@ -66,8 +66,6 @@ const Index = React.createClass({ const Contact = withRouter( React.createClass({ - displayName: 'Contact', - getStateFromStore(props) { const { id } = props ? props.params : this.props.params @@ -124,8 +122,6 @@ const Contact = withRouter( const NewContact = withRouter( React.createClass({ - displayName: 'NewContact', - createContact(event) { event.preventDefault() diff --git a/examples/passing-props-to-children/app.js b/examples/passing-props-to-children/app.js index b514d54a10..eef4842de5 100644 --- a/examples/passing-props-to-children/app.js +++ b/examples/passing-props-to-children/app.js @@ -6,8 +6,6 @@ import './app.css' const App = withRouter( React.createClass({ - displayName: 'App', - getInitialState() { return { tacos: [ diff --git a/upgrade-guides/v2.4.0.md b/upgrade-guides/v2.4.0.md index 7959d1bd46..14a7791180 100644 --- a/upgrade-guides/v2.4.0.md +++ b/upgrade-guides/v2.4.0.md @@ -12,8 +12,6 @@ import { withRouter } from 'react-router' const Page = React.createClass({ - displayName: 'Page', - componentDidMount() { this.props.router.setRouteLeaveHook(this.props.route, () => { if (this.state.unsaved)