├── .gitignore ├── .lvimrc ├── src ├── index.js ├── RouteTransitions.js ├── TransitionableComponent.js └── TransitionableSwitch.js ├── examples ├── Home.js ├── index.html ├── Menu.js ├── index.js ├── About.js └── Contact.js ├── .editorconfig ├── .babelrc ├── rollup.config.js ├── package.json └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | dist 2 | node_modules 3 | .cache 4 | -------------------------------------------------------------------------------- /.lvimrc: -------------------------------------------------------------------------------- 1 | let g:ale_fixers = { 2 | \ 'javascript': ['prettier_standard'], 3 | \} 4 | 5 | let g:ale_linters = {'javascript': ['']} 6 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | export RouteTransitions from './RouteTransitions' 2 | export TransitionableSwitch from './TransitionableSwitch' 3 | export TransitionableComponent from './TransitionableComponent' 4 | -------------------------------------------------------------------------------- /src/RouteTransitions.js: -------------------------------------------------------------------------------- 1 | export default { 2 | WILL_APPEAR: 'willAppear', 3 | WILL_ENTER: 'willEnter', 4 | WILL_LEAVE: 'willLeave', 5 | APPEAR: 'appear', 6 | ENTER: 'enter', 7 | LEAVE: 'leave' 8 | } 9 | -------------------------------------------------------------------------------- /examples/Home.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import TransitionableComponent from '../src/TransitionableComponent' 3 | 4 | export default class Home extends TransitionableComponent { 5 | render () { 6 | return

Home

7 | } 8 | } 9 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | indent_size = 2 6 | end_of_line = lf 7 | charset = utf-8 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true 10 | 11 | [*.md] 12 | trim_trailing_whitespace = false 13 | -------------------------------------------------------------------------------- /examples/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | ["env", { 4 | "modules": false, 5 | "loose": true, 6 | "targets": { 7 | "browsers": [">0.25%", "ie >= 11"] 8 | } 9 | }], 10 | "stage-0", 11 | "react" 12 | ], 13 | "plugins": [ 14 | "external-helpers" 15 | ] 16 | } 17 | -------------------------------------------------------------------------------- /examples/Menu.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { withRouter } from 'react-router' 3 | 4 | class Menu extends React.PureComponent { 5 | render () { 6 | return ( 7 | 14 | ) 15 | } 16 | } 17 | 18 | export default withRouter(Menu) 19 | -------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | import babel from 'rollup-plugin-babel' 2 | import builtins from 'rollup-plugin-node-builtins' 3 | import pkg from './package.json' 4 | 5 | const name = 'transitionableRoute' 6 | 7 | const output = { 8 | umd: pkg.main, 9 | es: pkg.module 10 | } 11 | 12 | export default { 13 | input: 'src/index.js', 14 | output: [ 15 | { 16 | file: output.umd, 17 | format: 'umd', 18 | name 19 | }, 20 | { 21 | file: output.es, 22 | format: 'es' 23 | } 24 | ], 25 | external: [...Object.keys(pkg.dependencies || {})], 26 | plugins: [ 27 | babel(), 28 | builtins() 29 | ] 30 | } 31 | -------------------------------------------------------------------------------- /examples/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { render } from 'react-dom' 3 | import { BrowserRouter, Route } from 'react-router-dom' 4 | import TransitionableSwitch from '../src/TransitionableSwitch' 5 | import Home from './Home' 6 | import About from './About' 7 | import Contact from './Contact' 8 | 9 | const App = () => ( 10 | 11 | 12 | } /> 13 | 14 | } /> 15 | 16 | 17 | 18 | ) 19 | 20 | render(, document.querySelector('[data-app]')) 21 | -------------------------------------------------------------------------------- /examples/About.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { TransitionableComponent } from '../dist/transitionableRoutes.umd.js' 3 | 4 | export default class About extends TransitionableComponent { 5 | constructor (props) { 6 | super(props) 7 | this.state = { transition: null } 8 | } 9 | 10 | willEnter (done) { 11 | this.setState({ transition: 'enter' }, done) 12 | } 13 | 14 | willLeave (done) { 15 | this.setState({ transition: 'leave' }, () => setTimeout(done, 500)) 16 | } 17 | 18 | render () { 19 | const { transition } = this.state 20 | const style = { 21 | opacity: transition === 'enter' ? 1 : 0, 22 | transition: 'opacity 0.5s ease-out' 23 | } 24 | return

About

25 | } 26 | } 27 | -------------------------------------------------------------------------------- /examples/Contact.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { TransitionableComponent } from '../dist/transitionableRoutes.umd.js' 3 | 4 | export default class Contact extends TransitionableComponent { 5 | constructor (props) { 6 | super(props) 7 | this.state = { transition: null } 8 | } 9 | 10 | willEnter (done) { 11 | this.setState({ transition: 'enter' }, done) 12 | } 13 | 14 | willLeave (done) { 15 | this.setState({ transition: 'leave' }, () => setTimeout(done, 1000)) 16 | } 17 | 18 | render () { 19 | const { transition } = this.state 20 | const style = { 21 | transform: `translateY(${transition === 'enter' ? 0 : '20px'})`, 22 | opacity: transition === 'enter' ? 1 : 0, 23 | transition: 'all 1s ease-out' 24 | } 25 | return

Contact

26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/TransitionableComponent.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import RouteTransitions from './RouteTransitions' 3 | 4 | export default class TransitionableComponent extends React.PureComponent { 5 | constructor (props) { 6 | super(props) 7 | 8 | this.willAppear = this.willAppear.bind(this) 9 | this.willEnter = this.willEnter.bind(this) 10 | this.willLeave = this.willLeave.bind(this) 11 | } 12 | 13 | componentWillMount () { 14 | this.props.emitter.once(RouteTransitions.WILL_APPEAR, this.willAppear) 15 | this.props.emitter.once(RouteTransitions.WILL_ENTER, this.willEnter) 16 | this.props.emitter.once(RouteTransitions.WILL_LEAVE, this.willLeave) 17 | } 18 | 19 | componentWillUnmount () { 20 | this.props.emitter.removeAllListeners() 21 | } 22 | 23 | willAppear (done) { 24 | done() 25 | } 26 | 27 | willEnter (done) { 28 | done() 29 | } 30 | 31 | willLeave (done) { 32 | done() 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "transitionable-routes", 3 | "version": "0.2.0", 4 | "description": "Perform transitions when changing routes with React Router", 5 | "license": "MIT", 6 | "repository": "github:rafaelrinaldi/transitionable-routes", 7 | "main": "dist/transitionableRoutes.umd.js", 8 | "jsnext:main": "dist/transitionableRoutes.es.js", 9 | "module": "dist/transitionableRoutes.es.js", 10 | "keywords": [ 11 | "animation", 12 | "animations", 13 | "motion", 14 | "react", 15 | "react-router", 16 | "route", 17 | "router", 18 | "routes", 19 | "transition", 20 | "transitions" 21 | ], 22 | "scripts": { 23 | "build": "rollup -c", 24 | "test": "NODE_ENV=test prettier-standard index.js && jest --verbose", 25 | "start": "NODE_ENV=development parcel examples/index.html -p 8000" 26 | }, 27 | "files": [ 28 | "dist" 29 | ], 30 | "devDependencies": { 31 | "babel-core": "^6.26.3", 32 | "babel-plugin-external-helpers": "^6.22.0", 33 | "babel-preset-env": "^1.7.0", 34 | "babel-preset-react": "^6.24.1", 35 | "babel-preset-stage-0": "^6.24.1", 36 | "jest-cli": "^23.4.1", 37 | "prettier-standard": "^8.0.1", 38 | "rollup": "^0.63.4", 39 | "rollup-plugin-babel": "^3.0.7", 40 | "rollup-plugin-node-builtins": "^2.1.2" 41 | }, 42 | "optionalDependencies": { 43 | "parcel-bundler": "^1.6.2", 44 | "react-dom": "^16.2.0" 45 | }, 46 | "dependencies": { 47 | "react": "^16.4.1", 48 | "react-router": "^4.3.1", 49 | "react-router-dom": "^4.3.1" 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [animate]: http://animate.mhaagens.me 2 | [events]: https://github.com/Gozala/events 3 | [potato]: https://github.com/codify-to/Potato 4 | [react-router-v4-transition]: https://github.com/aboeglin/react-router-v4-transition 5 | [react-router]: https://reacttraining.com/react-router 6 | [react-transition-group]: https://github.com/reactjs/react-transition-group 7 | [react]: https://reactjs.org 8 | [redux]: https://redux.js.org 9 | [robotlegs]: http://www.robotlegs.org 10 | [url]: https://rinaldi.io 11 | 12 | # transitionable-routes ![Experimental](https://img.shields.io/badge/stability-experimental-orange.svg) 13 | 14 | > Perform transitions when changing routes with React Router 15 | 16 | # Install 17 | 18 | ```sh 19 | npm i transitionable-routes 20 | ``` 21 | 22 | ## The Problem 23 | 24 | The ability to add animations whenever a route changes seems like such a trivial feature but weirdly enough I haven't found a nice way of doing it with [React][react] and [React Router][react-router]. 25 | 26 | All the examples I've seen out there – including the ones in the official docs – usually assume you are performing generic transitions such as fading in and out the routes entering and leaving. If you need more control over that, good luck. 27 | 28 | At some point in time this was achievable via [react-transition-group][react-transition-group] but at some point React Router had a major bump that broke integration with it and as a workaround the react-transition-group team changed the API in a way that for me was a downgrade since they've made much harder to make things customizable (they've added a `in` property that makes no sense to me and instead of controlling animations via callbacks you had to pass down how long transitions would take, which is an idea that I dislike very much). 29 | 30 | Coming from a Flash background I remember this used to be a feature we took for granted. Either you rolled your own at the beginning of the project or you would have it available via frameworks (shout out to [Potato (aka Patota)][potato] and [Robotlegs][robotlegs]). 31 | 32 | ## The Solution 33 | 34 | >Keep in mind that this was put together in a few hours and is still experimental. If you have ideas on how to improve it, do chime in. 35 | 36 | Out of desperation (I couldn't imagine this would take longer than a few minutes to ship) I decided to hack my way into a solution. I have started by writing the API that I wanted then started putting something together off of some good ideas that I've seen in the wild. 37 | 38 | ### `TransitionableSwitch` 39 | 40 | This is a simple stateful React component that acts as a replacement for React Router's `Switch`. It knows how to render route components based on the active route, but it also knows how to coordinate rendering of routes that are transitioning (either entering or leaving). 41 | 42 | This component injects hooks to every route component that is transitioning so inside of it you have access to transition states. 43 | 44 | The coordination of transition states is done via [event emitters][events]. These are responsible for communicating state from the switcher down to the component. 45 | I have tried using React `ref` for this but had a bad experience, specially when you're trying to wrap a [Redux][redux]-connected component, so I went for good ol' event emitters (which still sounds kinda crazy but did the job well). 46 | 47 | ### `TransitionableComponent` 48 | 49 | This is a simple React component that automatically handles syncing the component to the event emitter and exposes all the hooks available for transitions: 50 | 51 | * `willAppear` 52 | * `willEnter` 53 | * `willLeave` 54 | 55 | All hooks receive a callback function as an argument so you can do whatever you want and call them once you're done. 56 | 57 | ## Usage 58 | 59 | ```jsx 60 | import React from 'react' 61 | import { render } from 'react-dom' 62 | import { BrowserRouter, Route } from 'react-router-dom' 63 | import { TransitionableSwitch, TransitionableComponent } from 'transitionable-routes' 64 | 65 | const Home = () =>

Home

66 | 67 | class About extends TransitionableComponent { 68 | constructor (props) { 69 | super(props) 70 | this.state = { transition: null } 71 | } 72 | 73 | willEnter (done) { 74 | this.setState({ transition: 'enter' }, done) 75 | } 76 | 77 | willLeave (done) { 78 | this.setState({ transition: 'leave' }, () => setTimeout(done, 500)) 79 | } 80 | 81 | render () { 82 | const { transition } = this.state 83 | const style = { 84 | opacity: transition === 'enter' ? 1 : 0, 85 | transition: 'opacity 0.5s ease-out' 86 | } 87 | return

About

88 | } 89 | } 90 | 91 | class Contact extends TransitionableComponent { 92 | constructor (props) { 93 | super(props) 94 | this.state = { transition: null } 95 | } 96 | 97 | willEnter (done) { 98 | this.setState({ transition: 'enter' }, done) 99 | } 100 | 101 | willLeave (done) { 102 | this.setState({ transition: 'leave' }, () => setTimeout(done, 1000)) 103 | } 104 | 105 | render () { 106 | const { transition } = this.state 107 | const style = { 108 | transform: `translateY(${transition === 'enter' ? 0 : '20px'})`, 109 | opacity: transition === 'enter' ? 1 : 0, 110 | transition: 'all 1s ease-out' 111 | } 112 | return

Contact

113 | } 114 | } 115 | 116 | const App = () => ( 117 | 118 | 119 | } /> 120 | 121 | } /> 122 | 123 | 124 | ) 125 | 126 | render(, document.querySelector('[data-app]')) 127 | ``` 128 | 129 | Checkout the [examples folder](./examples). 130 | 131 | ```sh 132 | npm start 133 | ``` 134 | 135 | ### Conditional rendering 136 | 137 | It's very common to want to conditionally render a route. Say you want to redirect logged out users to a sign in page instead of giving guest access to private routes: 138 | 139 | ```js 140 | import React from 'react' 141 | import { Redirect, Route } from 'react-router-dom' 142 | import SuperSecretRoute from './components/SuperSecretRoute' 143 | 144 | const withAuth = (props, Component) => { 145 | // If user is logged in, we're good to go, just return the component itself 146 | if (isUserLoggedIn) return 147 | 148 | // It's very handy to have "referrer" when you're redirecting, so we use local state for that 149 | const referrer = props.path || props.location.pathname 150 | 151 | // Return a `Redirect` component pointing to a sign in page 152 | return ( 153 | 159 | ); 160 | }; 161 | 162 | const App = () => ( 163 | 164 | withAuth(props, SuperSecretRoute)} /> 165 | 166 | ); 167 | ``` 168 | 169 | ## Project Pipeline 170 | 171 | ![Help Wanted](http://messages.hellobits.com/warning.svg?message=Help%20Wanted) 172 | 173 | ### API 174 | 175 | Even though we have `TransitionableComponent` it still feels like we could improve the API since there's quite a lot of boilerplate involved as it is right now. To create a route component that is able to perform transitions: 176 | 177 | 1. Create a new React component that extends `TransitionableComponent` 178 | 2. Override transition lifecycle methods and fire `done()` 179 | 3. If your component needs to do anything on either `componentWillMount()` or `componentWillUnmount()` you have to remember to invoke `super()` otherwise things will break 180 | 4. Whenever implementing a transition you must make your component stateful and manually change transition steps so you can do your thing on `render()`, this can become annoying if you have a lot of custom transitions 181 | 182 | As mentioned on #1 this could perhaps be improved with HOCs instead of using a class like `TransitionableComponent` but I'm not sure yet on what's the best thing to do there. 183 | 184 | ### Examples 185 | 186 | Current examples are super simple and limited. It would be nice to have better ones that properly showcase this project's capabilities. 187 | 188 | ## Related 189 | 190 | * [animate][animate] 191 | * [react-router-v4-transition][react-router-v4-transition] 192 | 193 | ## License 194 | 195 | MIT © [Rafael Rinaldi][url] 196 | 197 | --- 198 | 199 |

200 | Buy me a ☕ 201 |

202 | -------------------------------------------------------------------------------- /src/TransitionableSwitch.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { matchPath, withRouter } from 'react-router' 3 | import EventEmitter from 'events' 4 | import RouteTransitions from './RouteTransitions' 5 | 6 | /** 7 | * This is a replacement for React Router's `Switch` component. 8 | * The difference is that this will inject transition hooks to route components. 9 | * By using event emitters and exposing a simple API, this gives enough control to enable 10 | * transitions between routes. 11 | * 12 | * This work started of as a modification of `react-router-v4-transition`. 13 | */ 14 | 15 | class TransitionableSwitch extends React.Component { 16 | constructor (props) { 17 | super(props) 18 | 19 | // Local reference to route components 20 | this._componentEntering = null 21 | this._componentLeaving = null 22 | 23 | // Hash map with all event emitters by route key 24 | this._emitters = {} 25 | 26 | this.state = { 27 | enteringRouteKey: null, 28 | leavingRouteKey: null, 29 | match: null 30 | } 31 | } 32 | 33 | /** 34 | * Given a route, returns a component. 35 | * This accounts for all possible ways React Route offers to render a component. 36 | */ 37 | _getComponentFromRoute (route) { 38 | const { props } = route 39 | 40 | if (props.component) return React.createElement(props.component) 41 | if (props.render) return props.render(props) 42 | return props.children 43 | } 44 | 45 | /** 46 | * Given a route key, returns the event emitter instance for it. 47 | * If none is found, creates a new one. 48 | */ 49 | _getEmitterByRouteKey (key) { 50 | if (!this._emitters.hasOwnProperty(key)) { 51 | this._emitters[key] = new EventEmitter() 52 | } 53 | return this._emitters[key] 54 | } 55 | 56 | /** 57 | * Creates a transitionable component. 58 | * All it does is to clone a React Component and inject key and emitter props to it. 59 | */ 60 | _createTransitionableComponent (Component, options) { 61 | const { key, path, ...props } = options 62 | const emitter = this._getEmitterByRouteKey(key) 63 | 64 | return React.cloneElement(Component, { 65 | emitter, 66 | key, 67 | path, 68 | ...props 69 | }) 70 | } 71 | 72 | /** 73 | * Helper to submit an event to a transitionable component. 74 | * This also figures out what the target should be by analyzing the event being sent. 75 | */ 76 | _emit (event, payload) { 77 | // There's only two possible options: entering or leaving 78 | const mapEventToTarget = event => 79 | /appear|enter$/i.test(event) 80 | ? this._componentEntering 81 | : this._componentLeaving 82 | const target = mapEventToTarget(event) 83 | 84 | if (!target) return 85 | 86 | const { emitter, path } = target.props 87 | 88 | // Wait for browser's next tick to emit the event so we can animate properly 89 | return window.requestAnimationFrame(() => { 90 | emitter.emit(event, payload) 91 | }) 92 | } 93 | 94 | _updateChildren (props) { 95 | let hasFoundMatch = false 96 | 97 | const routes = React.Children.map(props.children, route => route) 98 | 99 | routes.forEach(route => { 100 | const pathData = { 101 | path: route.props.path, 102 | exact: route.props.exact, 103 | strict: route.props.strict 104 | } 105 | 106 | const { location } = props 107 | 108 | const match = matchPath(location.pathname, pathData) 109 | 110 | if (!hasFoundMatch && match) { 111 | hasFoundMatch = true 112 | 113 | // If route is already rendered, do nothing 114 | if (this.state.enteringRouteKey === route.key) return 115 | 116 | // Update `_componentLeaving` as the new `_componentEntering` 117 | if (!this.state.leavingRouteKey) { 118 | this._componentLeaving = this._componentEntering 119 | this._componentEntering = null 120 | } 121 | 122 | const leavingRouteKey = this.state.leavingRouteKey 123 | ? this.state.leavingRouteKey 124 | : this.state.enteringRouteKey 125 | 126 | this.setState({ 127 | leavingRouteKey, 128 | enteringRouteKey: route.key, 129 | match 130 | }) 131 | } 132 | }) 133 | 134 | // In case we didn't find a match, the `_componentEntering` will leave: 135 | if (!hasFoundMatch && this.state.enteringRouteKey) { 136 | this._componentLeaving = this._componentEntering 137 | this._componentEntering = null 138 | 139 | this.setState({ 140 | leavingRouteKey: this.state.enteringRouteKey, 141 | enteringRouteKey: null, 142 | match: null 143 | }) 144 | } 145 | } 146 | 147 | _componentDidAppear () { 148 | this._emit(RouteTransitions.APPEAR) 149 | } 150 | 151 | _componentDidEnter () { 152 | /** 153 | * Scroll window to top-left when changing routes 154 | */ 155 | window.scrollTo({ 156 | left: 0, 157 | top: 0, 158 | behavior: 'instant' 159 | }) 160 | 161 | this._emit(RouteTransitions.ENTER) 162 | } 163 | 164 | // TODO: Add ability to not unmount on leave 165 | _componentDidLeave () { 166 | this._emit(RouteTransitions.LEAVE) 167 | 168 | this._componentLeaving = null 169 | this.setState({ leavingRouteKey: null }) 170 | } 171 | 172 | componentWillMount () { 173 | // We must initialize the current route before mounting it 174 | this._updateChildren(this.props) 175 | } 176 | 177 | componentWillReceiveProps (nextProps) { 178 | this._updateChildren(nextProps) 179 | } 180 | 181 | componentDidMount () { 182 | if (this._componentEntering) { 183 | this._emit(RouteTransitions.WILL_APPEAR, () => this._componentDidAppear()) 184 | } else { 185 | this._componentDidAppear() 186 | } 187 | } 188 | 189 | componentDidUpdate (prevProps, prevState) { 190 | const isEnteringNewRoute = 191 | prevState.enteringRouteKey === this.state.enteringRouteKey 192 | const hasLeftPreviousRoute = 193 | prevState.leavingRouteKey && !this.state.leavingRouteKey 194 | const shouldEmitWillEnter = isEnteringNewRoute && hasLeftPreviousRoute 195 | const shouldEmitWillLeave = 196 | this._componentLeaving && !prevState.leavingRouteKey 197 | const isSameLocation = 198 | prevProps.location.pathname === this.props.location.pathname 199 | const isSameMatch = prevProps.match.isExact === this.props.match.isExact 200 | 201 | if (shouldEmitWillEnter) { 202 | this._emit(RouteTransitions.WILL_ENTER, () => this._componentDidEnter()) 203 | } 204 | 205 | // If the location didn't change we do nothing and let the eventual active transitions run 206 | if (isSameLocation && isSameMatch) return 207 | 208 | // If there's no component leaving, it means it already left 209 | if (!this._componentLeaving) this._componentDidLeave() 210 | 211 | if (shouldEmitWillLeave) { 212 | this._emit(RouteTransitions.WILL_LEAVE, () => this._componentDidLeave()) 213 | } 214 | } 215 | 216 | render () { 217 | const props = { 218 | match: this.state.match, 219 | location: this.props.location, 220 | history: this.context.history, 221 | staticContext: this.context.staticContext 222 | } 223 | 224 | // Map all routes and filter it by the ones we care about 225 | const routes = React.Children.map( 226 | this.props.children, 227 | route => route 228 | ).filter(route => 229 | [this.state.enteringRouteKey, this.state.leavingRouteKey].includes( 230 | route.key 231 | ) 232 | ) 233 | 234 | /** 235 | * In order to support React Router's redirection, we must test this use case upfront. 236 | * If any of the matching routes is `Redirect` than it gets computed then exits. 237 | */ 238 | const Redirect = routes.find(route => { 239 | const Component = this._getComponentFromRoute(route) 240 | 241 | /** 242 | * Ideally here I would like to test against the type of the return value of a component, 243 | * similar to shallow rendering. 244 | * 245 | * Since `Redirect` returns null it would just work but on top of that it would allow users 246 | * to wrap and compose them which would be nicer and more flexible. 247 | * 248 | * However I haven't found a reliable way to perform such test so for now we require that 249 | * a component named `Redirect` is used, otherwise it won't work. 250 | * 251 | * Another known issue about relying only on the component type name exposed by React's data 252 | * structure is that it will most definitely break depending on the compression settings you 253 | * have set for your production build. By default most compressors will mangle classes and 254 | * function names. 255 | * 256 | * Babel's default React preset will override `displayName` as well which makes it 257 | * not reliable for this use case either. 258 | * 259 | * These are the reason why not only we check against the `type` property but also for the 260 | * presence of a `to` React prop. 261 | */ 262 | const isRedirect = 263 | /^redirect$/i.test(Component.type.name) || 264 | Component.props.hasOwnProperty('to') 265 | 266 | return isRedirect 267 | }) 268 | 269 | if (Redirect) { 270 | // Force `RouteTransitions.LEAVE` so it doesn't get stuck waiting for it to be triggered. 271 | this._componentDidLeave() 272 | 273 | return Redirect 274 | } 275 | 276 | routes.forEach(route => { 277 | const Component = this._getComponentFromRoute(route) 278 | 279 | const transitionableComponent = this._createTransitionableComponent( 280 | Component, 281 | { 282 | key: route.key, 283 | path: route.props.path, 284 | ...props 285 | } 286 | ) 287 | 288 | if (route.key === this.state.enteringRouteKey) { 289 | this._componentEntering = transitionableComponent 290 | } else if (route.key === this.state.leavingRouteKey) { 291 | this._componentLeaving = transitionableComponent 292 | } 293 | }) 294 | 295 | // Only renders `_componentEntering` when `_componentLeaving` left 296 | if (this.state.leavingRouteKey) this._componentEntering = null 297 | 298 | return ( 299 | 300 | {this._componentEntering} 301 | {this._componentLeaving} 302 | 303 | ) 304 | } 305 | } 306 | 307 | export default withRouter(TransitionableSwitch) 308 | --------------------------------------------------------------------------------