From 99360993b27e7a8de824db4e0c83e4212416a161 Mon Sep 17 00:00:00 2001 From: Stijn de Witt Date: Mon, 18 Apr 2016 22:18:13 +0200 Subject: [PATCH 1/2] Expanded API docs for `context.router` (#3346) * Expanded API docs for `context.router` Explain usage of `context.router` for: * Components created with `React.createClass` * Components created with ES classes extending `React.Component` * Components created as stateless functions * Changes based on feedback from code review * Replaced occurances of `React.PropTypes.object.isRequired` with `Router.PropTypes.router` * Expanded abbreviation 'i.c.w' to 'in combination with' https://github.com/reactjs/react-router/pull/3346 --- docs/API.md | 43 ++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 42 insertions(+), 1 deletion(-) diff --git a/docs/API.md b/docs/API.md index 6e6d2895ae..a2bfdbd442 100644 --- a/docs/API.md +++ b/docs/API.md @@ -165,12 +165,53 @@ An `` is like a [``](#link), except it is only active when the ### `` A `` renders the component tree for a given router state. Its used by `` but also useful for server rendering and integrating in brownfield development. -It also provides a `router` object on `context`. +It also provides a `router` object on [context](https://facebook.github.io/react/docs/context.html). #### `context.router` 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: + +```js +var MyComponent = React.createClass({ + contextTypes: { + router: Router.PropTypes.router + }, + render: function() { + // here, you can use `this.context.router` + } +}); + +``` + +Using `context.router` in combination with ES6 classes requires a different pattern (note the use of the `static` keyword): + +```js +class MyComponent extends React.Component { + static contextTypes = { + router: Router.PropTypes.router + } + + render: function() { + // here, you can use `this.context.router` + } +}); + +``` + +Finally, you can use `context.router` with +[stateless function components](https://facebook.github.io/react/docs/reusable-components.html#stateless-functions): + +```js +function MyComponent(props, context) { + // here, you can use `context.router` +} +MyComponent.contextTypes = { + router: Router.PropTypes.router +} +``` + ##### `push(pathOrLoc)` Transitions to a new URL, adding a new entry in the browser history. From a11a10bdd17a15ec7f0e7ab846d631677ac96baa Mon Sep 17 00:00:00 2001 From: Jimmy Jia Date: Mon, 18 Apr 2016 16:27:32 -0400 Subject: [PATCH 2/2] Clean up exported PropTypes (#3349) --- docs/API.md | 13 ++-- docs/Troubleshooting.md | 2 +- docs/guides/ConfirmingNavigation.md | 2 +- .../auth-flow-async-with-query-params/app.js | 8 +- examples/auth-flow/app.js | 4 +- examples/confirming-navigation/app.js | 4 +- examples/master-detail/app.js | 8 +- examples/passing-props-to-children/app.js | 4 +- modules/History.js | 2 +- modules/IndexRedirect.js | 2 +- modules/IndexRoute.js | 2 +- modules/InternalPropTypes.js | 22 ++++++ modules/Link.js | 3 +- modules/PropTypes.js | 78 +++++++++++++------ modules/Redirect.js | 2 +- modules/Route.js | 2 +- modules/Router.js | 2 +- modules/__tests__/RouterContext-test.js | 4 +- modules/__tests__/transitionHooks-test.js | 6 +- modules/index.js | 2 +- upgrade-guides/v2.0.0.md | 10 +-- 21 files changed, 119 insertions(+), 63 deletions(-) create mode 100644 modules/InternalPropTypes.js diff --git a/docs/API.md b/docs/API.md index a2bfdbd442..917ce1e5ef 100644 --- a/docs/API.md +++ b/docs/API.md @@ -680,14 +680,11 @@ One or many [``](#route)s or [`PlainRoute`](#plainroute)s. ### `PropTypes` -The following objects are exposed as properties of the exported PropTypes object: -- `falsy`: Checks that a component does not have a prop -- `history` -- `location` -- `component` -- `components` -- `route` -- `routes` +The following prop types are exported at top level and from `react-router/lib/PropTypes`: +- `routerShape`: Shape for the `router` object on context +- `locationShape`: Shape for the `location` object on route component props + +Previously, a number of prop types intended for internal use were also exported under `PropTypes`. These are deprecated and should not be used. ### `useRoutes(createHistory)` (deprecated) diff --git a/docs/Troubleshooting.md b/docs/Troubleshooting.md index 6d579f7428..016c17fc8f 100644 --- a/docs/Troubleshooting.md +++ b/docs/Troubleshooting.md @@ -6,7 +6,7 @@ You need to add `router` to your component's `contextTypes` to make the router o ```js contextTypes: { - router: Router.PropTypes.router + router: routerShape.isRequired } ``` diff --git a/docs/guides/ConfirmingNavigation.md b/docs/guides/ConfirmingNavigation.md index af087f1cab..5561696024 100644 --- a/docs/guides/ConfirmingNavigation.md +++ b/docs/guides/ConfirmingNavigation.md @@ -6,7 +6,7 @@ You can prevent a transition from happening or prompt the user before leaving a const Home = React.createClass({ contextTypes: { - router: Router.PropTypes.router + router: routerShape.isRequired }, componentDidMount() { diff --git a/examples/auth-flow-async-with-query-params/app.js b/examples/auth-flow-async-with-query-params/app.js index 7276a1d14b..4877bcab7d 100644 --- a/examples/auth-flow-async-with-query-params/app.js +++ b/examples/auth-flow-async-with-query-params/app.js @@ -1,6 +1,8 @@ -import React, { createClass, PropTypes } from 'react' +import React, { createClass } from 'react' import { render } from 'react-dom' -import { Router, Route, IndexRoute, browserHistory, Link } from 'react-router' +import { + Router, Route, IndexRoute, browserHistory, Link, routerShape +} from 'react-router' function App(props) { return ( @@ -12,7 +14,7 @@ function App(props) { const Form = createClass({ contextTypes: { - router: PropTypes.object.isRequired + router: routerShape.isRequired }, getInitialState() { diff --git a/examples/auth-flow/app.js b/examples/auth-flow/app.js index 176255cfe2..6df7a6c242 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 } from 'react-router' +import { browserHistory, Router, Route, Link, routerShape } from 'react-router' import auth from './auth' const App = React.createClass({ @@ -58,7 +58,7 @@ const Dashboard = React.createClass({ const Login = React.createClass({ contextTypes: { - router: React.PropTypes.object.isRequired + router: routerShape.isRequired }, getInitialState() { diff --git a/examples/confirming-navigation/app.js b/examples/confirming-navigation/app.js index e0a7584852..ad32a3e780 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 } from 'react-router' +import { browserHistory, Router, Route, Link, routerShape } from 'react-router' const App = React.createClass({ render() { @@ -24,7 +24,7 @@ const Dashboard = React.createClass({ const Form = React.createClass({ contextTypes: { - router: React.PropTypes.object.isRequired + router: routerShape.isRequired }, componentWillMount() { diff --git a/examples/master-detail/app.js b/examples/master-detail/app.js index 4bc3d9080a..6123378314 100644 --- a/examples/master-detail/app.js +++ b/examples/master-detail/app.js @@ -1,6 +1,8 @@ import React from 'react' import { render, findDOMNode } from 'react-dom' -import { browserHistory, Router, Route, IndexRoute, Link } from 'react-router' +import { + browserHistory, Router, Route, IndexRoute, Link, routerShape +} from 'react-router' import ContactStore from './ContactStore' import './app.css' @@ -63,7 +65,7 @@ const Index = React.createClass({ const Contact = React.createClass({ contextTypes: { - router: React.PropTypes.object.isRequired + router: routerShape.isRequired }, getStateFromStore(props) { @@ -120,7 +122,7 @@ const Contact = React.createClass({ const NewContact = React.createClass({ contextTypes: { - router: React.PropTypes.object.isRequired + router: routerShape.isRequired }, createContact(event) { diff --git a/examples/passing-props-to-children/app.js b/examples/passing-props-to-children/app.js index c5b812835f..ba98d42501 100644 --- a/examples/passing-props-to-children/app.js +++ b/examples/passing-props-to-children/app.js @@ -1,11 +1,11 @@ import React from 'react' import { render } from 'react-dom' -import { browserHistory, Router, Route, Link } from 'react-router' +import { browserHistory, Router, Route, Link, routerShape } from 'react-router' import './app.css' const App = React.createClass({ contextTypes: { - router: React.PropTypes.object.isRequired + router: routerShape.isRequired }, getInitialState() { diff --git a/modules/History.js b/modules/History.js index fa0542e557..f864d08b0d 100644 --- a/modules/History.js +++ b/modules/History.js @@ -1,5 +1,5 @@ import warning from './routerWarning' -import { history } from './PropTypes' +import { history } from './InternalPropTypes' /** * A mixin that adds the "history" instance variable to components. diff --git a/modules/IndexRedirect.js b/modules/IndexRedirect.js index 299f3aa4a4..730837f29e 100644 --- a/modules/IndexRedirect.js +++ b/modules/IndexRedirect.js @@ -2,7 +2,7 @@ import React from 'react' import warning from './routerWarning' import invariant from 'invariant' import Redirect from './Redirect' -import { falsy } from './PropTypes' +import { falsy } from './InternalPropTypes' const { string, object } = React.PropTypes diff --git a/modules/IndexRoute.js b/modules/IndexRoute.js index 0a884eb5ec..62d561d689 100644 --- a/modules/IndexRoute.js +++ b/modules/IndexRoute.js @@ -2,7 +2,7 @@ import React from 'react' import warning from './routerWarning' import invariant from 'invariant' import { createRouteFromReactElement } from './RouteUtils' -import { component, components, falsy } from './PropTypes' +import { component, components, falsy } from './InternalPropTypes' const { func } = React.PropTypes diff --git a/modules/InternalPropTypes.js b/modules/InternalPropTypes.js new file mode 100644 index 0000000000..9539552ec8 --- /dev/null +++ b/modules/InternalPropTypes.js @@ -0,0 +1,22 @@ +import { PropTypes } from 'react' + +const { func, object, arrayOf, oneOfType, element, shape, string } = PropTypes + +export function falsy(props, propName, componentName) { + if (props[propName]) + return new Error(`<${componentName}> should not have a "${propName}" prop`) +} + +export const history = shape({ + listen: func.isRequired, + push: func.isRequired, + replace: func.isRequired, + go: func.isRequired, + goBack: func.isRequired, + goForward: func.isRequired +}) + +export const component = oneOfType([ func, string ]) +export const components = oneOfType([ component, object ]) +export const route = oneOfType([ object, element ]) +export const routes = oneOfType([ route, arrayOf(route) ]) diff --git a/modules/Link.js b/modules/Link.js index 5f47a83255..95f7eb0a3a 100644 --- a/modules/Link.js +++ b/modules/Link.js @@ -1,5 +1,6 @@ import React from 'react' import warning from './routerWarning' +import { routerShape } from './PropTypes' const { bool, object, string, func, oneOfType } = React.PropTypes @@ -49,7 +50,7 @@ function createLocationDescriptor(to, { query, hash, state }) { const Link = React.createClass({ contextTypes: { - router: object + router: routerShape }, propTypes: { diff --git a/modules/PropTypes.js b/modules/PropTypes.js index e3a6c6b169..5a439ad58a 100644 --- a/modules/PropTypes.js +++ b/modules/PropTypes.js @@ -1,22 +1,22 @@ import { PropTypes } from 'react' -const { func, object, arrayOf, oneOfType, element, shape, string } = PropTypes +import deprecateObjectProperties from './deprecateObjectProperties' +import * as InternalPropTypes from './InternalPropTypes' +import warning from './routerWarning' -export function falsy(props, propName, componentName) { - if (props[propName]) - return new Error(`<${componentName}> should not have a "${propName}" prop`) -} +const { func, object, shape, string } = PropTypes -export const history = shape({ - listen: func.isRequired, +export const routerShape = shape({ push: func.isRequired, replace: func.isRequired, go: func.isRequired, goBack: func.isRequired, - goForward: func.isRequired + goForward: func.isRequired, + setRouteLeaveHook: func.isRequired, + isActive: func.isRequired }) -export const location = shape({ +export const locationShape = shape({ pathname: string.isRequired, search: string.isRequired, state: object, @@ -24,23 +24,55 @@ export const location = shape({ key: string }) -export const component = oneOfType([ func, string ]) -export const components = oneOfType([ component, object ]) -export const route = oneOfType([ object, element ]) -export const routes = oneOfType([ route, arrayOf(route) ]) +// Deprecated stuff below: -export const router = shape({ - push: func.isRequired, - replace: func.isRequired, - go: func.isRequired, - goBack: func.isRequired, - goForward: func.isRequired, - setRouteLeaveHook: func.isRequired, - isActive: func.isRequired -}) +export let falsy = InternalPropTypes.falsy +export let history = InternalPropTypes.history +export let location = locationShape +export let component = InternalPropTypes.component +export let components = InternalPropTypes.components +export let route = InternalPropTypes.route +export let routes = InternalPropTypes.routes +export let router = routerShape + +if (__DEV__) { + const deprecatePropType = (propType, message) => (...args) => { + warning(false, message) + return propType(...args) + } + + const deprecateInternalPropType = propType => ( + deprecatePropType(propType, 'This prop type is not intended for external use, and was previously exported by mistake. These internal prop types are deprecated for external use, and will be removed in a later version.') + ) + + const deprecateRenamedPropType = (propType, name) => ( + deprecatePropType(propType, `The \`${name}\` prop type is now exported as \`${name}Shape\` to avoid name conflicts. This export is deprecated and will be removed in a later version.`) + ) + + falsy = deprecateInternalPropType(falsy) + history = deprecateInternalPropType(history) + component = deprecateInternalPropType(component) + components = deprecateInternalPropType(components) + route = deprecateInternalPropType(route) + routes = deprecateInternalPropType(routes) -export default { + location = deprecateRenamedPropType(location, 'location') + router = deprecateRenamedPropType(router, 'router') +} + +let defaultExport = { + falsy, history, location, + component, + components, + route, + // For some reason, routes was never here. router } + +if (__DEV__) { + defaultExport = deprecateObjectProperties(defaultExport, 'The default export from `react-router/lib/PropTypes` is deprecated. Please use the named exports instead.') +} + +export default defaultExport diff --git a/modules/Redirect.js b/modules/Redirect.js index 09737b836b..7a8333fbfc 100644 --- a/modules/Redirect.js +++ b/modules/Redirect.js @@ -2,7 +2,7 @@ import React from 'react' import invariant from 'invariant' import { createRouteFromReactElement } from './RouteUtils' import { formatPattern } from './PatternUtils' -import { falsy } from './PropTypes' +import { falsy } from './InternalPropTypes' const { string, object } = React.PropTypes diff --git a/modules/Route.js b/modules/Route.js index 3b554ccec7..dbb7840bb7 100644 --- a/modules/Route.js +++ b/modules/Route.js @@ -1,7 +1,7 @@ import React from 'react' import invariant from 'invariant' import { createRouteFromReactElement } from './RouteUtils' -import { component, components } from './PropTypes' +import { component, components } from './InternalPropTypes' const { string, func } = React.PropTypes diff --git a/modules/Router.js b/modules/Router.js index 8a4091f8e8..5444674329 100644 --- a/modules/Router.js +++ b/modules/Router.js @@ -3,7 +3,7 @@ import useQueries from 'history/lib/useQueries' import React from 'react' import createTransitionManager from './createTransitionManager' -import { routes } from './PropTypes' +import { routes } from './InternalPropTypes' import RouterContext from './RouterContext' import { createRoutes } from './RouteUtils' import { createRouterObject, createRoutingHistory } from './RouterUtils' diff --git a/modules/__tests__/RouterContext-test.js b/modules/__tests__/RouterContext-test.js index 55010ed532..7ef14493df 100644 --- a/modules/__tests__/RouterContext-test.js +++ b/modules/__tests__/RouterContext-test.js @@ -3,7 +3,7 @@ import React from 'react' import { render, unmountComponentAtNode } from 'react-dom' import match from '../match' -import { router as routerPropType } from '../PropTypes' +import { routerShape } from '../PropTypes' import RouterContext from '../RouterContext' import { createRouterObject } from '../RouterUtils' @@ -42,7 +42,7 @@ describe('RouterContext', () => { } Component.contextTypes = { - router: routerPropType.isRequired + router: routerShape.isRequired } routes = { path: '/', component: Component } diff --git a/modules/__tests__/transitionHooks-test.js b/modules/__tests__/transitionHooks-test.js index 0fa02755cf..3e6b6c392b 100644 --- a/modules/__tests__/transitionHooks-test.js +++ b/modules/__tests__/transitionHooks-test.js @@ -2,7 +2,7 @@ import expect, { spyOn } from 'expect' import React, { Component } from 'react' import { render, unmountComponentAtNode } from 'react-dom' import createHistory from '../createMemoryHistory' -import { router as routerPropType } from '../PropTypes' +import { routerShape } from '../PropTypes' import execSteps from './execSteps' import Router from '../Router' @@ -43,7 +43,7 @@ describe('When a router enters a branch', function () { } NewsFeed.contextTypes = { - router: routerPropType.isRequired + router: routerShape.isRequired } class Inbox extends Component { @@ -72,7 +72,7 @@ describe('When a router enters a branch', function () { } User.contextTypes = { - router: routerPropType.isRequired + router: routerShape.isRequired } NewsFeedRoute = { diff --git a/modules/index.js b/modules/index.js index b82e692dcb..56761bad18 100644 --- a/modules/index.js +++ b/modules/index.js @@ -19,7 +19,7 @@ export useRoutes from './useRoutes' export { createRoutes } from './RouteUtils' export RouterContext from './RouterContext' export RoutingContext from './RoutingContext' -export PropTypes from './PropTypes' +export PropTypes, { locationShape, routerShape } from './PropTypes' export match from './match' export useRouterHistory from './useRouterHistory' export { formatPattern } from './PatternUtils' diff --git a/upgrade-guides/v2.0.0.md b/upgrade-guides/v2.0.0.md index 4f509d086c..b93bc7193e 100644 --- a/upgrade-guides/v2.0.0.md +++ b/upgrade-guides/v2.0.0.md @@ -112,7 +112,7 @@ Access `location` from `this.props.location` of your `Route` component. If you'd // v2.0.x const RouteComponent = React.createClass({ childContextTypes: { - location: Router.PropTypes.location + location: React.PropTypes.object }, getChildContext() { @@ -136,7 +136,7 @@ const RouteComponent = React.createClass({ // 2.0.0 const RouteComponent = React.createClass({ contextTypes: { - route: Router.PropTypes.route + route: React.PropTypes.object }, getChildContext() { return { @@ -160,7 +160,7 @@ const RouteComponent = React.createClass({ // v2.0.0 const RouteComponent = React.createClass({ contextTypes: { - router: Router.PropTypes.router.isRequired + router: React.PropTypes.object.isRequired }, componentDidMount() { const { route } = this.props @@ -204,7 +204,7 @@ const RouteComponent = React.createClass({ // v2.0.0 const RouteComponent = React.createClass({ contextTypes: { - router: Router.PropTypes.router.isRequired + router: React.PropTypes.object.isRequired }, someHandler() { this.context.router.push(...) @@ -229,7 +229,7 @@ const DeepComponent = React.createClass({ // 1) Use context.router (especially if on the server) const DeepComponent = React.createClass({ contextTypes: { - router: Router.PropTypes.router.isRequired + router: React.PropTypes.object.isRequired }, handleSubmit() { this.context.router.push(...)