diff --git a/packages/react-router-config/README.md b/packages/react-router-config/README.md index 37de863dc7..8d54b32a2b 100644 --- a/packages/react-router-config/README.md +++ b/packages/react-router-config/README.md @@ -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 @@ -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): @@ -35,9 +35,9 @@ 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. @@ -45,30 +45,34 @@ This project seeks to define a shared format for others to build patterns on top Routes are objects with the same properties as a `` 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`) +* 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 ```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 ``, relative paths are not (yet) supported. When it is supported there, it will be supported here. @@ -80,12 +84,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], @@ -95,12 +100,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 `` render methods. +* `routes`: A reference to the routes array used to match +* `match`: The match object that also gets passed to `` render methods. ```js -branch[0].match.url -branch[0].match.isExact +branch[0].match.url; +branch[0].match.isExact; // etc. ``` @@ -134,18 +139,18 @@ class PendingNavDataLoader extends Component { previousLocation: null } - componentWillReceiveProps(nextProps) { - const navigated = nextProps.location !== this.props.location + componentDidUpdate(prevProps) { + const navigated = prevProps.location !== this.props.location const { routes } = this.props if (navigated) { // save the location so we can render the old screen this.setState({ - previousLocation: this.props.location + previousLocation: prevProps.location }) // load data while the old screen remains - loadNextData(routes, nextProps.location).then((data) => { + loadNextData(routes, this.props.location).then((data) => { putTheDataSomewhereRoutesCanFindIt(data) // clear previousLocation so the next screen renders this.setState({ @@ -191,26 +196,30 @@ Again, that's all pseudo-code. There are a lot of ways to do server rendering wi 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 `` inside your components. You can render a `` 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: "/child/:id", component: Child, routes: [ - { path: '/child/:id/grand-child', + { + path: "/child/:id/grand-child", component: GrandChild } ] } ] } -] +]; const Root = ({ route }) => (
@@ -218,36 +227,34 @@ const Root = ({ route }) => ( {/* child routes won't render without this */} {renderRoutes(route.routes)}
-) +); const Home = ({ route }) => (

Home

-) +); const Child = ({ route }) => (

Child

{/* 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" })}
-) +); const GrandChild = ({ someProp }) => (

Grand Child

{someProp}
-) - +); -ReactDOM.render(( +ReactDOM.render( {/* kick it all off with the root route */} {renderRoutes(routes)} - -), document.getElementById('root')) - + , + document.getElementById("root") +); ``` - diff --git a/packages/react-router-dom/modules/BrowserRouter.js b/packages/react-router-dom/modules/BrowserRouter.js index 3bf05d2f23..9a97e67033 100644 --- a/packages/react-router-dom/modules/BrowserRouter.js +++ b/packages/react-router-dom/modules/BrowserRouter.js @@ -18,7 +18,7 @@ class BrowserRouter extends React.Component { history = createHistory(this.props); - componentWillMount() { + componentDidMount() { warning( !this.props.history, " ignores the history prop. To use a custom history, " + diff --git a/packages/react-router-dom/modules/HashRouter.js b/packages/react-router-dom/modules/HashRouter.js index 26f822728b..6ed287cd3e 100644 --- a/packages/react-router-dom/modules/HashRouter.js +++ b/packages/react-router-dom/modules/HashRouter.js @@ -17,7 +17,7 @@ class HashRouter extends React.Component { history = createHistory(this.props); - componentWillMount() { + componentDidMount() { warning( !this.props.history, " ignores the history prop. To use a custom history, " + diff --git a/packages/react-router-native/docs/guides/animation.md b/packages/react-router-native/docs/guides/animation.md index 8e7a27ebb6..658e5d7146 100644 --- a/packages/react-router-native/docs/guides/animation.md +++ b/packages/react-router-native/docs/guides/animation.md @@ -5,7 +5,7 @@ This guide is a little sparse right now, but should provide enough insight to he # Element Transitions As the user navigates, some elements should animate while remaining on the -page. The [`Route`][Route] `children` prop is perfect for these situations. +page. The [`Route`][route] `children` prop is perfect for these situations. Consider this app without the router. When the `` is pressed the sidebar's animation will toggle. @@ -18,10 +18,10 @@ class Sidebar extends Component { ) } - componentWillReceiveProps(nextProps) { - if (nextProps.isOpen !== this.props.isOpen) { + componentDidUpdate(prevProps) { + if (prevProps.isOpen !== this.props.isOpen) { Animated.timing(this.state.anim, { - toValue: nextProps.isOpen ? 1 : 0 + toValue: this.props.isOpen ? 1 : 0 }).start() } } @@ -60,18 +60,21 @@ class App extends Component { render() { return ( - ( - // `children` always renders, match or not. This - // way we can always render the sidebar, and then - // tell it if its open or not - - )}/> + ( + // `children` always renders, match or not. This + // way we can always render the sidebar, and then + // tell it if its open or not + + )} + /> Open Sidebar - + - ) + ); } } ``` @@ -85,9 +88,7 @@ header. {chutneys.map(chutney => ( - {({ match }) => ( - - )} + {({ match }) => } ))} @@ -97,9 +98,9 @@ Each chutney has its own route thats always rendering as part of the list, when ## Page Transitions -Because of components' declarative nature, when you're at one screen, press a link, and navigate to another, the old page is not in the render tree to even animate anymore! The key is remembering that React elements are just objects. You can save them and render them again. That's the strategy for animating from one page (that leaves the render tree) to another. +Because of components' declarative nature, when you're at one screen, press a link, and navigate to another, the old page is not in the render tree to even animate anymore! The key is remembering that React elements are just objects. You can save them and render them again. That's the strategy for animating from one page (that leaves the render tree) to another. -If you visited this site on mobile, or you shrink the browser really small, you can click the back button to see this type of animation. The strategy is to not think about animations at first. Just render your routes and links and make that all work, then wrap your components with animated components to spiff things up. +If you visited this site on mobile, or you shrink the browser really small, you can click the back button to see this type of animation. The strategy is to not think about animations at first. Just render your routes and links and make that all work, then wrap your components with animated components to spiff things up. We'll consider some child routes in a page: @@ -109,18 +110,17 @@ class Parent extends Component { return ( - - + + - ) + ); } } ``` Once that works without animations, we're ready to add an animation around it. - ```jsx - - + + ``` -It's important to use a [``][Switch]. It will ensure that only one route can match, and therefore gives us a single element on `props.children` to hang on to and render during the animation. Finally, you *must* pass the location to `Switch`. It prefers `props.location` over the internal router location, which enables the saved child element to be renered later and continue to match the old location. +It's important to use a [``][switch]. It will ensure that only one route can match, and therefore gives us a single element on `props.children` to hang on to and render during the animation. Finally, you _must_ pass the location to `Switch`. It prefers `props.location` over the internal router location, which enables the saved child element to be renered later and continue to match the old location. There are a handful of props handed to `AnimatedChild` that the parent will know about as it manages the animation. Again, this guide is more inspiration than copy/paste right now, feel free to look at the source of this website for exact implementation. Alright, let's check out the implementation of `AnimatedChild` (it's copy pasted from the animation used on this site). ```jsx class AnimatedChild extends Component { - static propTypes = { children: PropTypes.node, anim: PropTypes.object, atParent: PropTypes.bool, animating: PropTypes.bool - } + }; state = { // we're going to save the old children so we can render // it when it doesnt' actually match the location anymore previousChildren: null - } + }; - componentWillReceiveProps(nextProps) { + componentDidUpdate(prevProps) { // figure out what to do with the children - const navigatingToParent = nextProps.atParent && !this.props.atParent - const animationEnded = this.props.animating && !nextProps.animating + const navigatingToParent = prevProps.atParent && !this.props.atParent; + const animationEnded = this.props.animating && !prevProps.animating; if (navigatingToParent) { // we were rendering, but now we're heading back up to the parent, // so we need to save the children (har har) so we can render them // while the animation is playing this.setState({ - previousChildren: this.props.children - }) + previousChildren: prevProps.children + }); } else if (animationEnded) { // When we're done animating, we can get rid of the old children. this.setState({ previousChildren: null - }) + }); } } render() { - const { anim, children } = this.props - const { previousChildren } = this.state + const { anim, children } = this.props; + const { previousChildren } = this.state; return ( - + {/* render the old ones if we have them */} {previousChildren || children} - ) + ); } } ``` -Hope that helps get you thinking. Again, the animations themselves are the same with the router or not, the difference is knowing when to trigger them. Here's a list for things to check in `componentWillReceiveProps` to decide what to do with an animation based on the router's location: +Hope that helps get you thinking. Again, the animations themselves are the same with the router or not, the difference is knowing when to trigger them. Here's a list for things to check in `componentDidUpdate` to decide what to do with an animation based on the router's location: General change in location ```js -nextProps.location !== this.props.location` +prevProps.location !== this.props.location` ``` Going from child to parent: ```js -nextProps.match.isExact && !this.props.match.isExact +prevProps.match.isExact && !this.props.match.isExact; ``` Going from parent to child: ```js -!nextProps.match.isExact && this.props.match.isExact +!prevProps.match.isExact && this.props.match.isExact; ``` Good luck! We hope to expand on this section with a lot more detail and live examples. - [Route]:../api/Route.md - [Switch]:../api/Switch.md +[route]: ../api/Route.md +[switch]: ../api/Switch.md diff --git a/packages/react-router-native/experimental/StackRoute.js b/packages/react-router-native/experimental/StackRoute.js index eb7138be15..9541daa150 100644 --- a/packages/react-router-native/experimental/StackRoute.js +++ b/packages/react-router-native/experimental/StackRoute.js @@ -5,8 +5,8 @@ // Any help cleaning it up would be appreciated. // <3 <3 <3 - Ryan -import React, { Component } from 'react' -import PropTypes from 'prop-types' +import React, { Component } from "react"; +import PropTypes from "prop-types"; import { Text, @@ -17,27 +17,27 @@ import { Dimensions, Animated, PanResponder -} from 'react-native' +} from "react-native"; -import Route from 'react-router/Route' -import Redirect from 'react-router/Redirect' -import Link from '../Link' +import Route from "react-router/Route"; +import Redirect from "react-router/Redirect"; +import Link from "../Link"; -const { any, node } = PropTypes +const { any, node } = PropTypes; class StackContainer extends Component { static contextTypes = { stack: any - } + }; static childContextTypes = { stack: any - } + }; initialLocation = { ...this.props.location, pathname: this.props.match.url - } + }; getChildContext() { return { @@ -45,51 +45,49 @@ class StackContainer extends Component { ...this.context.stack, parentLocation: this.initialLocation } - } + }; } - componentWillMount() { - this.pushToStack('down') + componentDidMount() { + this.pushToStack("down"); } componentDidUpdate(prevProps) { - const becameActive = ( - this.props.match.isExact === true && - prevProps.match.isExact === false - ) + const becameActive = + this.props.match.isExact === true && prevProps.match.isExact === false; if (becameActive) { - this.pushToStack('up') + this.pushToStack("up"); } } pushToStack(direction) { - const { renderTitle, renderContent, renderChild, ...rest } = this.props - const { match } = rest + const { renderTitle, renderContent, renderChild, ...rest } = this.props; + const { match } = rest; if (match && match.isExact) { this.context.stack.push({ title: renderTitle(rest), content: renderContent(rest), parentLocation: this.context.stack.parentLocation, direction - }) + }); } } render() { - const { renderTitle, renderContent, renderChild, ...rest } = this.props - const { match } = rest - return match.isExact ? null : renderChild ? renderChild(rest) : null + const { renderTitle, renderContent, renderChild, ...rest } = this.props; + const { match } = rest; + return match.isExact ? null : renderChild ? renderChild(rest) : null; } } -const ANIMATION_DURATION = 300 -const CANCEL_PAN_MOVE_THRESHOLD = 0.5 -const CANCEL_PAN_VX_THRESHOLD = 0.5 -const PAN_START_MARGIN = 30 -const PARENT_TRAVEL_DISTANCE = 100 -const PARENT_FINAL_OPACITY = 0.25 -const CARD_SHADOW_RADIUS = 10 +const ANIMATION_DURATION = 300; +const CANCEL_PAN_MOVE_THRESHOLD = 0.5; +const CANCEL_PAN_VX_THRESHOLD = 0.5; +const PAN_START_MARGIN = 30; +const PARENT_TRAVEL_DISTANCE = 100; +const PARENT_FINAL_OPACITY = 0.25; +const CARD_SHADOW_RADIUS = 10; class AnimatedStack extends React.Component { static propTypes = { @@ -100,264 +98,286 @@ class AnimatedStack extends React.Component { location: any, onPanBackGrant: any, onCancelPan: any - } + }; state = { previousProps: null, panning: false, cancelingPan: false - } + }; - animation = new Animated.Value(0) + animation = new Animated.Value(0); panResponder = PanResponder.create({ - onMoveShouldSetPanResponderCapture: (e, g) => { return ( this.props.parentLocation && e.nativeEvent.locationX <= PAN_START_MARGIN && g.dx > 0 - ) + ); }, onPanResponderGrant: (e, g) => { - this.setState({ - startPan: true, - panStartLeft: g.moveX - }, () => { - this.props.onPanBackGrant() - }) + this.setState( + { + startPan: true, + panStartLeft: g.moveX + }, + () => { + this.props.onPanBackGrant(); + } + ); }, - onPanResponderMove: Animated.event([ null, { moveX: this.animation } ]), + onPanResponderMove: Animated.event([null, { moveX: this.animation }]), onPanResponderRelease: (e, g) => { - const { panStartLeft } = this.state - const { width } = Dimensions.get('window') - const releaseRatio = g.moveX/width - const isCancel = g.vx < CANCEL_PAN_VX_THRESHOLD && releaseRatio < CANCEL_PAN_MOVE_THRESHOLD + const { panStartLeft } = this.state; + const { width } = Dimensions.get("window"); + const releaseRatio = g.moveX / width; + const isCancel = + g.vx < CANCEL_PAN_VX_THRESHOLD && + releaseRatio < CANCEL_PAN_MOVE_THRESHOLD; if (isCancel) { - this.animation.setValue(g.moveX - panStartLeft) - this.setState({ - panning: false, - cancelingPan: true - }, () => { - Animated.timing(this.animation, { - toValue: 0, - duration: releaseRatio * ANIMATION_DURATION + 2000 - }).start(({ finished }) => { - this.props.onCancelPan() - }) - }) + this.animation.setValue(g.moveX - panStartLeft); + this.setState( + { + panning: false, + cancelingPan: true + }, + () => { + Animated.timing(this.animation, { + toValue: 0, + duration: releaseRatio * ANIMATION_DURATION + 2000 + }).start(({ finished }) => { + this.props.onCancelPan(); + }); + } + ); } else { - this.animation.setValue(g.moveX - panStartLeft) + this.animation.setValue(g.moveX - panStartLeft); this.setState({ panning: false }, () => { Animated.timing(this.animation, { toValue: width, - duration: (1-releaseRatio) * ANIMATION_DURATION + 2000 + duration: (1 - releaseRatio) * ANIMATION_DURATION + 2000 }).start(({ finished }) => { this.setState({ previousProps: null - }) - }) - }) + }); + }); + }); } } - }) + }); - componentWillReceiveProps(nextProps) { - if (nextProps.location !== this.props.location) { + componentDidUpdate(prevProps) { + if (prevProps.location !== this.props.location) { if (this.state.cancelingPan) { // location comes in after the animation is done, // so we remove previousProps after the router transition this.setState({ cancelingPan: false, previousProps: null - }) + }); } else if (this.state.startPan) { // don't do "timing" animation when we start to pan, let // the user slide it around this.setState({ startPan: false, panning: true, - previousProps: this.props - }) + previousProps: prevProps + }); } else { // normal case, new location shows up, so we animate - const { width } = Dimensions.get('window') - this.setState({ - previousProps: this.props - }, () => { - this.animation.setValue(0) - Animated.timing(this.animation, { - toValue: width, - duration: ANIMATION_DURATION - }).start(({ finished }) => { - this.setState({ previousProps: null }) - }) - }) + const { width } = Dimensions.get("window"); + this.setState( + { + previousProps: prevProps + }, + () => { + this.animation.setValue(0); + Animated.timing(this.animation, { + toValue: width, + duration: ANIMATION_DURATION + }).start(({ finished }) => { + this.setState({ previousProps: null }); + }); + } + ); } } } render() { - const { width, height } = Dimensions.get('window') - const { direction } = this.props - const { previousProps, panDx, panning, panStartLeft } = this.state - const animating = !!previousProps - const bothProps = [ this.props ] - if (animating) - bothProps.push(previousProps) + const { width, height } = Dimensions.get("window"); + const { direction } = this.props; + const { previousProps, panDx, panning, panStartLeft } = this.state; + const animating = !!previousProps; + const bothProps = [this.props]; + if (animating) bothProps.push(previousProps); return ( - + {bothProps.map((props, index, arr) => { - const isParent = index === 0 - const transitioning = arr.length > 1 + const isParent = index === 0; + const transitioning = arr.length > 1; return ( - {props.parentLocation ? props.backButton :  } - - - {props.title} + {props.parentLocation ? ( + props.backButton + ) : ( +   + )} - + {props.title} + - ) + ); })} - + {bothProps.map((props, index, arr) => { - const isParent = index === 0 - const transitioning = arr.length > 1 + const isParent = index === 0; + const transitioning = arr.length > 1; return ( - + {props.content} - )})} + ); + })} - ) + ); } } -const rootStoredLocations = {} +const rootStoredLocations = {}; class StackRootContainer extends Component { static childContextTypes = { - stack: any, - } + stack: any + }; static contextTypes = { router: PropTypes.object - } + }; static propTypes = { children: PropTypes.node, location: PropTypes.object, path: PropTypes.string - } + }; state = { title: null, @@ -365,7 +385,7 @@ class StackRootContainer extends Component { backLocation: null, backButton: null, direction: null - } + }; getChildContext() { return { @@ -381,30 +401,36 @@ class StackRootContainer extends Component { < ) : null - }) + }); } } - } + }; } componentWillUnmount() { - rootStoredLocations[this.props.path] = this.props.location + rootStoredLocations[this.props.path] = this.props.location; } handlePanBack = () => { if (this.state.parentLocation) { - this.panCancelLocation = this.props.location - this.context.router.history.replace(this.state.parentLocation) + this.panCancelLocation = this.props.location; + this.context.router.history.replace(this.state.parentLocation); } - } + }; handlePanCancel = () => { - this.context.router.history.replace(this.panCancelLocation) - } + this.context.router.history.replace(this.panCancelLocation); + }; render() { - const { title, content, backButton, parentLocation, direction } = this.state - const { children, location } = this.props + const { + title, + content, + backButton, + parentLocation, + direction + } = this.state; + const { children, location } = this.props; return ( @@ -420,17 +446,17 @@ class StackRootContainer extends Component { /> {children} - ) + ); } } class RedirectStack extends Component { - componentWillMount() { - delete rootStoredLocations[this.props.path] + componentDidMount() { + delete rootStoredLocations[this.props.path]; } render() { - return + return ; } } @@ -441,26 +467,29 @@ class StackRoute extends Component { renderTitle: any, renderContent: any, renderChild: any - } + }; render() { - const { isRoot, path, ...rest } = this.props + const { isRoot, path, ...rest } = this.props; return ( - ( - isRoot ? ( - rootStoredLocations[path] ? ( - + + isRoot ? ( + rootStoredLocations[path] ? ( + + ) : ( + + + + ) ) : ( - - - + ) - ) : ( - - ) - )}/> - ) + } + /> + ); } } -export default StackRoute +export default StackRoute; diff --git a/packages/react-router-redux/examples/AuthExample.js b/packages/react-router-redux/examples/AuthExample.js index 3f389d6e88..8b05efb775 100644 --- a/packages/react-router-redux/examples/AuthExample.js +++ b/packages/react-router-redux/examples/AuthExample.js @@ -1,114 +1,112 @@ -import React from 'react' -import { render } from 'react-dom' -import { connect, Provider } from 'react-redux' +import React from "react"; +import { render } from "react-dom"; +import { connect, Provider } from "react-redux"; import { ConnectedRouter, routerReducer, routerMiddleware, push -} from 'react-router-redux' +} from "react-router-redux"; -import { createStore, applyMiddleware, combineReducers } from 'redux' -import createHistory from 'history/createBrowserHistory' +import { createStore, applyMiddleware, combineReducers } from "redux"; +import createHistory from "history/createBrowserHistory"; -import { Route, Switch } from 'react-router' -import { Redirect } from 'react-router-dom' +import { Route, Switch } from "react-router"; +import { Redirect } from "react-router-dom"; -const history = createHistory() +const history = createHistory(); const authSuccess = () => ({ - type: 'AUTH_SUCCESS' -}) + type: "AUTH_SUCCESS" +}); const authFail = () => ({ - type: 'AUTH_FAIL' -}) + type: "AUTH_FAIL" +}); const initialState = { isAuthenticated: false -} +}; -const authReducer = (state = initialState , action) => { +const authReducer = (state = initialState, action) => { switch (action.type) { - case 'AUTH_SUCCESS': + case "AUTH_SUCCESS": return { ...state, isAuthenticated: true - } - case 'AUTH_FAIL': + }; + case "AUTH_FAIL": return { ...state, isAuthenticated: false - } + }; default: - return state + return state; } -} +}; const store = createStore( combineReducers({ routerReducer, authReducer }), - applyMiddleware(routerMiddleware(history)), -) + applyMiddleware(routerMiddleware(history)) +); class LoginContainer extends React.Component { render() { - return + return ; } } class HomeContainer extends React.Component { - componentWillMount() { - alert('Private home is at: ' + this.props.location.pathname) + componentDidMount() { + alert("Private home is at: " + this.props.location.pathname); } render() { - return + return ; } } class PrivateRouteContainer extends React.Component { render() { - const { - isAuthenticated, - component: Component, - ...props - } = this.props + const { isAuthenticated, component: Component, ...props } = this.props; return ( - isAuthenticated - ? - : ( - + isAuthenticated ? ( + + ) : ( + ) } /> - ) + ); } } const PrivateRoute = connect(state => ({ isAuthenticated: state.authReducer.isAuthenticated -}))(PrivateRouteContainer) +}))(PrivateRouteContainer); const Login = connect(null, dispatch => ({ login: () => { - dispatch(authSuccess()) - dispatch(push('/')) + dispatch(authSuccess()); + dispatch(push("/")); } -}))(LoginContainer) +}))(LoginContainer); const Home = connect(null, dispatch => ({ logout: () => { - dispatch(authFail()) - dispatch(push('/login')) + dispatch(authFail()); + dispatch(push("/login")); } -}))(HomeContainer) +}))(HomeContainer); render( @@ -119,5 +117,5 @@ render( , - document.getElementById('root'), -) + document.getElementById("root") +); diff --git a/packages/react-router-redux/modules/ConnectedRouter.js b/packages/react-router-redux/modules/ConnectedRouter.js index 62b8794bb5..322225e3cf 100644 --- a/packages/react-router-redux/modules/ConnectedRouter.js +++ b/packages/react-router-redux/modules/ConnectedRouter.js @@ -16,17 +16,9 @@ class ConnectedRouter extends Component { store: PropTypes.object }; - handleLocationChange = (location, action) => { - this.store.dispatch({ - type: LOCATION_CHANGE, - payload: { - location, - action - } - }); - }; + constructor(props, context) { + super(props, context); - componentWillMount() { const { store: propsStore, history, isSSR } = this.props; this.store = propsStore || this.context.store; @@ -36,6 +28,16 @@ class ConnectedRouter extends Component { this.handleLocationChange(history.location); } + handleLocationChange = (location, action) => { + this.store.dispatch({ + type: LOCATION_CHANGE, + payload: { + location, + action + } + }); + }; + componentWillUnmount() { if (this.unsubscribeFromHistory) this.unsubscribeFromHistory(); } diff --git a/packages/react-router/docs/api/history.md b/packages/react-router/docs/api/history.md index 82396dc71e..7dbf266923 100644 --- a/packages/react-router/docs/api/history.md +++ b/packages/react-router/docs/api/history.md @@ -4,25 +4,25 @@ The term "history" and "`history` object" in this documentation refers to [the ` The following terms are also used: -- "browser history" - A DOM-specific implementation, useful in web browsers that support the HTML5 history API -- "hash history" - A DOM-specific implementation for legacy web browsers -- "memory history" - An in-memory history implementation, useful in testing and non-DOM environments like React Native +* "browser history" - A DOM-specific implementation, useful in web browsers that support the HTML5 history API +* "hash history" - A DOM-specific implementation for legacy web browsers +* "memory history" - An in-memory history implementation, useful in testing and non-DOM environments like React Native `history` objects typically have the following properties and methods: -- `length` - (number) The number of entries in the history stack -- `action` - (string) The current action (`PUSH`, `REPLACE`, or `POP`) -- `location` - (object) The current location. May have the following properties: - - `pathname` - (string) The path of the URL - - `search` - (string) The URL query string - - `hash` - (string) The URL hash fragment - - `state` - (object) location-specific state that was provided to e.g. `push(path, state)` when this location was pushed onto the stack. Only available in browser and memory history. -- `push(path, [state])` - (function) Pushes a new entry onto the history stack -- `replace(path, [state])` - (function) Replaces the current entry on the history stack -- `go(n)` - (function) Moves the pointer in the history stack by `n` entries -- `goBack()` - (function) Equivalent to `go(-1)` -- `goForward()` - (function) Equivalent to `go(1)` -- `block(prompt)` - (function) Prevents navigation (see [the history docs](https://github.com/ReactTraining/history#blocking-transitions)) +* `length` - (number) The number of entries in the history stack +* `action` - (string) The current action (`PUSH`, `REPLACE`, or `POP`) +* `location` - (object) The current location. May have the following properties: + * `pathname` - (string) The path of the URL + * `search` - (string) The URL query string + * `hash` - (string) The URL hash fragment + * `state` - (object) location-specific state that was provided to e.g. `push(path, state)` when this location was pushed onto the stack. Only available in browser and memory history. +* `push(path, [state])` - (function) Pushes a new entry onto the history stack +* `replace(path, [state])` - (function) Replaces the current entry on the history stack +* `go(n)` - (function) Moves the pointer in the history stack by `n` entries +* `goBack()` - (function) Equivalent to `go(-1)` +* `goForward()` - (function) Equivalent to `go(1)` +* `block(prompt)` - (function) Prevents navigation (see [the history docs](https://github.com/ReactTraining/history#blocking-transitions)) ## history is mutable @@ -30,16 +30,17 @@ The history object is mutable. Therefore it is recommended to access the [`locat ```jsx class Comp extends React.Component { - componentWillReceiveProps(nextProps) { + componentDidUpdate(prevProps) { // will be true - const locationChanged = nextProps.location !== this.props.location + const locationChanged = prevProps.location !== this.props.location; // INCORRECT, will *always* be false because history is mutable. - const locationChanged = nextProps.history.location !== this.props.history.location + const locationChanged = + prevProps.history.location !== this.props.history.location; } } - +; ``` Additional properties may also be present depending on the implementation you're using. Please refer to [the history documentation](https://github.com/ReactTraining/history#properties) for more details. diff --git a/packages/react-router/docs/api/location.md b/packages/react-router/docs/api/location.md index c995835f99..ba0902ba0c 100644 --- a/packages/react-router/docs/api/location.md +++ b/packages/react-router/docs/api/location.md @@ -17,18 +17,18 @@ even where it was. It looks like this: The router will provide you with a location object in a few places: -- [Route component](./Route.md#component) as `this.props.location` -- [Route render](./Route.md#render-func) as `({ location }) => ()` -- [Route children](./Route.md#children-func) as `({ location }) => ()` -- [withRouter](./withRouter.md) as `this.props.location` +* [Route component](./Route.md#component) as `this.props.location` +* [Route render](./Route.md#render-func) as `({ location }) => ()` +* [Route children](./Route.md#children-func) as `({ location }) => ()` +* [withRouter](./withRouter.md) as `this.props.location` It is also found on `history.location` but you shouldn't use that because its mutable. You can read more about that in the [history](./history.md) doc. A location object is never mutated so you can use it in the lifecycle hooks to determine when navigation happens, this is really useful for data fetching and animation. ```js -componentWillReceiveProps(nextProps) { - if (nextProps.location !== this.props.location) { +componentDidUpdate(prevProps) { + if (prevProps.location !== this.props.location) { // navigated! } } @@ -36,11 +36,11 @@ componentWillReceiveProps(nextProps) { You can provide locations instead of strings to the various places that navigate: -- Web [Link to](../../../react-router-dom/docs/api/Link.md#to) -- Native [Link to](../../../react-router-native/docs/api/Link.md#to) -- [Redirect to](./Redirect.md#to) -- [history.push](./history.md#push) -- [history.replace](./history.md#push) +* Web [Link to](../../../react-router-dom/docs/api/Link.md#to) +* Native [Link to](../../../react-router-native/docs/api/Link.md#to) +* [Redirect to](./Redirect.md#to) +* [history.push](./history.md#push) +* [history.replace](./history.md#push) Normally you just use a string, but if you need to add some "location state" that will be available whenever the app returns to that specific location, you can use a location object instead. This is useful if you want to branch UI based on navigation history instead of just paths (like modals). @@ -62,8 +62,7 @@ history.replace(location) Finally, you can pass a location to the following components: -- [Route](./Route.md#location) -- [Switch](./Switch.md#location) +* [Route](./Route.md#location) +* [Switch](./Switch.md#location) This will prevent them from using the actual location in the router's state. This is useful for animation and pending navigation, or any time you want to trick a component into rendering at a different location than the real one. - diff --git a/packages/react-router/docs/guides/migrating.md b/packages/react-router/docs/guides/migrating.md index 8151828a9f..9911384930 100644 --- a/packages/react-router/docs/guides/migrating.md +++ b/packages/react-router/docs/guides/migrating.md @@ -9,7 +9,7 @@ React Router v4 is a complete rewrite, so there is not a simple migration path. * [The Router](#the-router) * [Routes](#routes) * [Nesting Routes](#nesting-routes) - * [on* properties](#on-properties) + * [on\* properties](#on-properties) * [Optional Parameters](#optional-parameters) * [Query Strings](#query-strings) * [Switch](#switch) @@ -43,8 +43,8 @@ In v4, there is no centralized route configuration. Anywhere that you need to re //v4
- - + +
``` @@ -90,9 +90,9 @@ The v4 `` component is actually a component, so wherever you render a ``s were nested by passing them as the `children` of their parent ``. ```jsx - - - + + + ``` @@ -107,21 +107,21 @@ When a nested `` matched, React elements would be created using both the With v4, children ``s should just be rendered by the parent ``'s component. ```jsx - +; const Parent = () => (
- - + +
-) +); ``` ### `on*` properties React Router v3 provides `onEnter`, `onUpdate`, and `onLeave` methods. These were essentially recreating React's lifecycle methods. -With v4, you should use the lifecycle methods of the component rendered by a ``. Instead of `onEnter`, you would use `componentDidMount` or `componentWillMount`. Where you would use `onUpdate`, you can use `componentDidUpdate` or `componentWillUpdate` (or possibly `componentWillReceiveProps`). `onLeave` can be replaced with `componentWillUnmount`. +With v4, you should use the lifecycle methods of the component rendered by a ``. Instead of `onEnter`, you would use `componentDidMount`. Where you would use `onUpdate`, you can use `componentDidUpdate` or `componentWillUpdate` (or possibly `componentWillReceiveProps` or `static getDerivedStateFromProps`). `onLeave` can be replaced with `componentWillUnmount`. ### Optional Parameters @@ -143,10 +143,10 @@ In v3, you could specify a number of child routes, and only the first one that m ```jsx // v3 - + - - + + ``` @@ -156,12 +156,11 @@ v4 provides a similar functionality with the `` component. When a ` ( - - - + + + -) - +); ``` ### `` @@ -173,7 +172,6 @@ In v3, if you wanted to redirect from one path to another, for instance / to /we - ``` In v4, you can achieve the same functionality using ``. @@ -187,7 +185,6 @@ In v4, you can achieve the same functionality using ``. - ``` In v3, `` preserved the query string: @@ -214,27 +211,29 @@ In v4, you must re-pass these properties to the `to` prop: ## PatternUtils ### matchPattern(pattern, pathname) + In v3, you could use the same matching code used internally to check if a path matched a pattern. In v4 this has been replaced by [matchPath](/packages/react-router/docs/api/matchPath.md) which is powered by the [`path-to-regexp@^1.7.0`](https://github.com/pillarjs/path-to-regexp/tree/v1.7.0) library. ### formatPattern(pattern, params) + In v3, you could use PatternUtils.formatPattern to generate a valid path from a path pattern (perhaps in a constant or in your central routing config) and an object containing the names parameters: ```jsx // v3 -const THING_PATH = '/thing/:id'; +const THING_PATH = "/thing/:id"; -A thing +A thing; ``` In v4, you can achieve the same functionality using the [`compile`](https://github.com/pillarjs/path-to-regexp/tree/v1.7.0#compile-reverse-path-to-regexp) function in [`path-to-regexp@^1.7.0`](https://github.com/pillarjs/path-to-regexp/tree/v1.7.0). ```jsx // v4 -const THING_PATH = '/thing/:id'; +const THING_PATH = "/thing/:id"; const thingPath = pathToRegexp.compile(THING_PATH); -A thing +A thing; ``` ### getParamNames @@ -244,6 +243,7 @@ The `getParamNames` functionality can be achieved using the [`parse`](https://gi ## Link ### `to` property is required + In v3, you could omit `to` property or set it to null to create an anchor tag without `href` attribute. ```jsx diff --git a/packages/react-router/modules/MemoryRouter.js b/packages/react-router/modules/MemoryRouter.js index 3922857f2f..5084328ba9 100644 --- a/packages/react-router/modules/MemoryRouter.js +++ b/packages/react-router/modules/MemoryRouter.js @@ -18,7 +18,7 @@ class MemoryRouter extends React.Component { history = createHistory(this.props); - componentWillMount() { + componentDidMount() { warning( !this.props.history, " ignores the history prop. To use a custom history, " + diff --git a/packages/react-router/modules/Prompt.js b/packages/react-router/modules/Prompt.js index 19f0b5157f..fc8e9ad9eb 100644 --- a/packages/react-router/modules/Prompt.js +++ b/packages/react-router/modules/Prompt.js @@ -35,7 +35,7 @@ class InnerPrompt extends React.Component { } } - componentWillMount() { + componentDidMount() { invariant( this.props.router, "You should not use outside a " @@ -44,10 +44,10 @@ class InnerPrompt extends React.Component { if (this.props.when) this.enable(this.props.message); } - componentWillReceiveProps(nextProps) { - if (nextProps.when) { - if (!this.props.when || this.props.message !== nextProps.message) - this.enable(nextProps.message); + componentDidUpdate(prevProps) { + if (this.props.when) { + if (!prevProps.when || prevProps.message !== this.props.message) + this.enable(this.props.message); } else { this.disable(); } diff --git a/packages/react-router/modules/Redirect.js b/packages/react-router/modules/Redirect.js index 1bd390aeef..b35d46de50 100644 --- a/packages/react-router/modules/Redirect.js +++ b/packages/react-router/modules/Redirect.js @@ -25,6 +25,12 @@ class InnerRedirect extends React.Component { }).isRequired }; + constructor(props) { + super(props); + + if (this.isStatic()) this.perform(); + } + static defaultProps = { push: false }; @@ -33,16 +39,11 @@ class InnerRedirect extends React.Component { return this.props.router && this.props.router.staticContext; } - componentWillMount() { + componentDidMount() { invariant( this.props.router, "You should not use outside a " ); - - if (this.isStatic()) this.perform(); - } - - componentDidMount() { if (!this.isStatic()) this.perform(); } @@ -78,7 +79,10 @@ class InnerRedirect extends React.Component { } perform() { - const { push, router: { history } } = this.props; + const { + push, + router: { history } + } = this.props; const to = this.computeTo(this.props); if (push) { diff --git a/packages/react-router/modules/Route.js b/packages/react-router/modules/Route.js index e4caf6940b..7551557f93 100644 --- a/packages/react-router/modules/Route.js +++ b/packages/react-router/modules/Route.js @@ -70,7 +70,7 @@ class InnerRoute extends React.Component { return matchPath(pathname, { path, strict, exact, sensitive }, route.match); } - componentWillMount() { + componentDidMount() { warning( !(this.props.component && this.props.render), "You should not use and in the same route; will be ignored" @@ -95,20 +95,39 @@ class InnerRoute extends React.Component { ); } - componentWillReceiveProps(nextProps) { + componentDidUpdate(prevProps) { warning( - !(nextProps.location && !this.props.location), + !(prevProps.location && !this.props.location), ' elements should not change from uncontrolled to controlled (or vice versa). You initially used no "location" prop and then provided one on a subsequent render.' ); warning( - !(!nextProps.location && this.props.location), + !(!prevProps.location && this.props.location), ' elements should not change from controlled to uncontrolled (or vice versa). You provided a "location" prop initially but omitted it on a subsequent render.' ); - this.setState({ - match: this.computeMatch(nextProps) + const newMatch = this.computeMatch(this.props, this.context.router); + let matchesAreTheSame = true; + + Object.keys(newMatch).forEach(key => { + if (typeof newMatch[key] !== "object") { + matchesAreTheSame = + matchesAreTheSame && newMatch[key] === this.state.match[key]; + } else { + Object.keys(newMatch[key]).forEach(subKey => { + matchesAreTheSame = + matchesAreTheSame && + this.state.match[key] && + newMatch[key][subKey] === this.state.match[key][subKey]; + }); + } }); + + if (!matchesAreTheSame) { + this.setState({ + match: newMatch + }); + } } renderChildren() { diff --git a/packages/react-router/modules/Router.js b/packages/react-router/modules/Router.js index ecbb4f9529..1dd3f3473a 100644 --- a/packages/react-router/modules/Router.js +++ b/packages/react-router/modules/Router.js @@ -18,6 +18,42 @@ class Router extends React.Component { router: PropTypes.object.isRequired }; + constructor(props, context) { + super(props, context); + + const { children, history } = props; + + invariant( + children == null || React.Children.count(children) === 1, + "A may have only one child element" + ); + + if (!this.isStatic()) { + // Do this here so we can setState when a changes the + // location in componentDidMount. This happens e.g. when doing + // server rendering using a . + this.unlisten = history.listen(() => { + this.setState({ + match: this.computeMatch(history.location.pathname) + }); + }); + } + } + + componentDidMount() { + if (this.isStatic()) { + const { history } = this.props; + // Do this here so we can setState when a changes the + // location in componentDidMount. This happens e.g. when doing + // server rendering using a . + this.unlisten = history.listen(() => { + this.setState({ + match: this.computeMatch(history.location.pathname) + }); + }); + } + } + getChildContext() { return { router: { @@ -44,27 +80,9 @@ class Router extends React.Component { }; } - componentWillMount() { - const { children, history } = this.props; - - invariant( - children == null || React.Children.count(children) === 1, - "A may have only one child element" - ); - - // Do this here so we can setState when a changes the - // location in componentWillMount. This happens e.g. when doing - // server rendering using a . - this.unlisten = history.listen(() => { - this.setState({ - match: this.computeMatch(history.location.pathname) - }); - }); - } - - componentWillReceiveProps(nextProps) { + componentDidUpdate(prevProps) { warning( - this.props.history === nextProps.history, + this.props.history === prevProps.history, "You cannot change " ); } @@ -81,6 +99,12 @@ class Router extends React.Component { ); } + + isStatic() { + return ( + this.context && this.context.router && this.context.router.staticContext + ); + } } export default Router; diff --git a/packages/react-router/modules/StaticRouter.js b/packages/react-router/modules/StaticRouter.js index 8f8d4d8f60..4f9c59f4b9 100644 --- a/packages/react-router/modules/StaticRouter.js +++ b/packages/react-router/modules/StaticRouter.js @@ -62,6 +62,16 @@ class StaticRouter extends React.Component { router: PropTypes.object.isRequired }; + constructor(props) { + super(props); + + warning( + !this.props.history, + " ignores the history prop. To use a custom history, " + + "use `import { Router }` instead of `import { StaticRouter as Router }`." + ); + } + getChildContext() { return { router: { @@ -90,14 +100,6 @@ class StaticRouter extends React.Component { handleBlock = () => noop; - componentWillMount() { - warning( - !this.props.history, - " ignores the history prop. To use a custom history, " + - "use `import { Router }` instead of `import { StaticRouter as Router }`." - ); - } - render() { const { basename, context, location, ...props } = this.props; diff --git a/packages/react-router/modules/Switch.js b/packages/react-router/modules/Switch.js index 338cd28a13..8c1adcac2c 100644 --- a/packages/react-router/modules/Switch.js +++ b/packages/react-router/modules/Switch.js @@ -17,21 +17,20 @@ class InnerSwitch extends React.Component { }).isRequired }; - componentWillMount() { - invariant( - this.props.router, - "You should not use outside a " - ); + constructor(props) { + super(props); + + invariant(props.router, "You should not use outside a "); } - componentWillReceiveProps(nextProps) { + componentDidUpdate(prevProps) { warning( - !(nextProps.location && !this.props.location), + !(prevProps.location && !this.props.location), ' elements should not change from uncontrolled to controlled (or vice versa). You initially used no "location" prop and then provided one on a subsequent render.' ); warning( - !(!nextProps.location && this.props.location), + !(!prevProps.location && this.props.location), ' elements should not change from controlled to uncontrolled (or vice versa). You provided a "location" prop initially but omitted it on a subsequent render.' ); } diff --git a/packages/react-router/modules/__tests__/SwitchMount-test.js b/packages/react-router/modules/__tests__/SwitchMount-test.js index fa19bcf806..47be77ad37 100644 --- a/packages/react-router/modules/__tests__/SwitchMount-test.js +++ b/packages/react-router/modules/__tests__/SwitchMount-test.js @@ -12,7 +12,7 @@ describe("A ", () => { let mountCount = 0; class App extends React.Component { - componentWillMount() { + componentDidMount() { mountCount++; } diff --git a/website/modules/animated/createAnimatedComponent.js b/website/modules/animated/createAnimatedComponent.js index 3f6fc60f4b..7cda4cd915 100644 --- a/website/modules/animated/createAnimatedComponent.js +++ b/website/modules/animated/createAnimatedComponent.js @@ -35,7 +35,7 @@ function createAnimatedComponent(Component: any): any { } } - componentWillMount() { + componentDidMount() { this.attachProps(this.props); } @@ -72,8 +72,8 @@ function createAnimatedComponent(Component: any): any { oldPropsAnimated && oldPropsAnimated.__detach(); } - componentWillReceiveProps(nextProps) { - this.attachProps(nextProps); + componentDidUpdate() { + this.attachProps(this.props); } render() { diff --git a/website/modules/components/Bundle.js b/website/modules/components/Bundle.js index ecc28b9608..9a45d897a5 100644 --- a/website/modules/components/Bundle.js +++ b/website/modules/components/Bundle.js @@ -5,13 +5,13 @@ class Bundle extends Component { mod: null }; - componentWillMount() { + componentDidMount() { this.load(this.props); } - componentWillReceiveProps(nextProps) { - if (nextProps.load !== this.props.load) { - this.load(nextProps); + componentDidUpdate(prevProps) { + if (prevProps.load !== this.props.load) { + this.load(this.props); } } diff --git a/website/modules/components/EnvironmentSmall.js b/website/modules/components/EnvironmentSmall.js index a8304b0bde..dda7228ffd 100644 --- a/website/modules/components/EnvironmentSmall.js +++ b/website/modules/components/EnvironmentSmall.js @@ -46,16 +46,16 @@ class EnvironmentSmall extends Component { }); } - componentWillReceiveProps(nextProps) { + componentDidUpdate(prevProps) { const { anim } = this.state; // only animate if we're going from here to direct child. // child to child we'll ignore const goingToChild = - nextProps.match.isExact === false && this.props.match.isExact === true; + this.props.match.isExact === false && prevProps.match.isExact === true; const comingFromChild = - nextProps.match.isExact === true && this.props.match.isExact === false; + this.props.match.isExact === true && prevProps.match.isExact === false; if (goingToChild || comingFromChild) { this.setState( @@ -102,7 +102,11 @@ class EnvironmentSmall extends Component { ( + render={({ + match: { + params: { mod } + } + }) => (
( + render={({ + match: { + params: { example } + } + }) => (
{getExampleTitle(data, example)}
@@ -122,7 +130,11 @@ class EnvironmentSmall extends Component { /> ( + render={({ + match: { + params: { mod } + } + }) => (
{getGuideTitle(data, mod)}
)} /> @@ -264,13 +276,13 @@ class AnimatedChild extends Component { previousChildren: null }; - componentWillReceiveProps(nextProps) { - const navigatingToParent = nextProps.atParent && !this.props.atParent; - const animationEnded = this.props.animating && !nextProps.animating; + componentDidUpdate(prevProps) { + const navigatingToParent = this.props.atParent && !prevProps.atParent; + const animationEnded = prevProps.animating && !this.props.animating; if (navigatingToParent) { this.setState({ - previousChildren: this.props.children + previousChildren: prevProps.children }); } else if (animationEnded) { this.setState({ @@ -328,13 +340,13 @@ class AnimatedChildHeader extends Component { previousChildren: null }; - componentWillReceiveProps(nextProps) { - const navigatingToParent = nextProps.atParent && !this.props.atParent; - const animationEnded = this.props.animating && !nextProps.animating; + componentDidUpdate(prevProps) { + const navigatingToParent = this.props.atParent && !prevProps.atParent; + const animationEnded = prevProps.animating && !this.props.animating; if (navigatingToParent) { this.setState({ - previousChildren: this.props.children + previousChildren: prevProps.children }); } else if (animationEnded) { this.setState({ diff --git a/website/modules/components/PanGesture.js b/website/modules/components/PanGesture.js index 3af5f2872b..e250921729 100644 --- a/website/modules/components/PanGesture.js +++ b/website/modules/components/PanGesture.js @@ -46,9 +46,9 @@ class PanGesture extends Component { } } - componentWillReceiveProps(next) { - if (this.props.when !== next.when) { - if (next.when) { + componentDidUpdate(prevProps) { + if (this.props.when !== prevProps.when) { + if (this.props.when) { this.listen(); } else { this.unlisten();