react-router: Animation Switch not compatible with react-transition-group v2

This is regarding - https://github.com/ReactTraining/react-router/issues/4351

So react-transition-group is superceeding react-addons-css-tranistion-group - https://github.com/reactjs/react-transition-group

Transition API v2 expects immediate children of <TransitionGroup> to be <CSSTransition>.

React Router <Switch> expects immediate children to be of type <Route>.

Does this mean it is not possible to do this with v2 transition api? I put some details/code here - https://github.com/reactjs/react-transition-group/issues/83

About this issue

  • Original URL
  • State: closed
  • Created 7 years ago
  • Comments: 27 (8 by maintainers)

Most upvoted comments

@DGizmo Seems like the problem is that ReactDOM.findDOMNode(this) returns null when your component switch to exit state.

As in facebook/react#4651, ReactDOM.findDOMNode will return null when the component is unmounted or the ref changes. Are you unmounting or re-referencing the DOM node inside your route?

I don’t know enough about your setup to give a suggestion, but in my personal experience, I always found it very helpful to wrap everything inside CSSTransition with a div. This will not only ensure that CSSTransition always have a DOM node to work on (so findDOMNode won’t return null), but also allow me to separate route transition and all the other animation within the route (otherwise, the animation will be applied to the outermost DOM node inside your route, which might not be what you want)

So here is how you can wrap your routes, notice the div with class WRAPPER:

 <TransitionGroup>
    <CSSTransition key={this.props.location.pathname.split('/')[1]} timeout={500} classNames="fadeTranslate" mountOnEnter={true} unmountOnExit={true}>
        <div className="WRAPPER">
            <Switch location={this.props.location}>
                <Route path="/" exact component={Home} />
                <Route path="/blog" component={Blog} />
                <Route path="/albumn" component={Albumn} />
            </Switch>
        </div>
    </CSSTransition>
</TransitionGroup>

And the classNames you give CSSTransition will be applied to this div:

image

Give it a try 😃. Let me know if the issue still persists.

Not sure whether this is what you want, but here is how I use react-transition-group v2 with react-router v4:

<TransitionGroup>
    <CSSTransition key={this.props.location.key} timeout={500} classNames="fading-animation-transition" mountOnEnter={true} unmountOnExit={true}>
        <Switch location={this.props.location}>
            <Route path="/" exact component={Home} />
            <Route path="/blog" component={Blog} />
            <Route path="/albumn" component={Albumn} />
        </Switch>
    </CSSTransition>
</TransitionGroup>

@hlehmann Yeah, that’s because location.key keeps changing every time the route gets an update. To solve this problem, you can try using this.props.location.pathname instead of this.props.location.key, and using split() to get the prefix. e.g.

<TransitionGroup>
    <CSSTransition key={this.props.location.pathname.split('/')[1]} timeout={500} classNames="fading-animation-transition" mountOnEnter={true} unmountOnExit={true}>
        <Switch location={this.props.location}>
            <Route path="/" exact component={Home} />
            <Route path="/blog" component={Blog} />
            <Route path="/albumn" component={Albumn} />
        </Switch>
    </CSSTransition>
</TransitionGroup>

This will trigger the animation if I switch from /blog to /albumn, but will not trigger if I switch from /blog to /blog/post

Hi. I followed the advice in this thread to get my Routes animating. I am having an issue with the <Redirect> component. Redirecting works, but I still get a warning in the console:

Warning: You tried to redirect to the same route you’re currently on: “/”

The warning is visible in this code sandbox when you enter a bad route. Is there another way I should use the Redirect?

@andrewcroce I can’t think of any elegant way to approach this… though one thing you can try is to define a custom map from pathname to corresponding css transition effect, e.g.:

const transitionMap = {
    blog: { classNames: 'fade-translate', timeout: 500 },
    albumn: { classNames: 'fade-in-out', timeout: 300 },
    account: { classNames: 'fade-translate', timeout: 550 },
};

and in your route definition:

render = () => {
    const current = this.props.location.pathname.split('/')[1];
    return (
        <TransitionGroup>
            <CSSTransition
                key={current}
                classNames="default-transition"
                timeout={300}
                {...transitionMap[current]}
                mountOnEnter={true}
                unmountOnExit={true}
            >
                <div>
                    <Switch location={this.props.location}>
                        <Route path="/blog" component={Blog} />
                        <Route path="/albumn" component={Albumn} />
                        <Route path="/about" component={About} />
                        <Route path="/account" component={Account} />
                    </Switch>
                </div>
            </CSSTransition>
        </TransitionGroup>
    );
};

If there is no special transition defined for a route, then default-transition will be used. Otherwiese, {...transitionMap[current]} will override previously defined transitions.

This might not be the best way to solve the problem, but it should work… 😛

Here is a working fade page transition example with React Router v4 and React Transition Group v2: https://codesandbox.io/s/4RAqrkRkn?view=preview

Related issue on how it was created.

Thanks so much @Horizon-Blue to help us brainstorm these ideas. I also have a need in the future for varied transitions per route, I will share my brainstorm here when I get to work on that. 😃

You need to make sure you’re assigning key props to everything. That’s the trick.

Thanks for this thread. It helped me a lot to understand my problem.

@andrewcroce here is the way I do to handle multiple transitions (in my case there can be multiple leaving transitions on the same page depending on the next state):

On the changing page trigger I push a new location to the history with a state:

<Button
    onClick={() => {
        history.push({
            pathname: '/next',
            state: {transition: 'fade', duration: 1000}
        })
    }
/>
    Go to next page
</Button>

Then I declared a Transition component (to understand this code you need to read this issue that was the end of my search for a solution):

import React from 'react'
import { TransitionGroup, CSSTransition } from 'react-transition-group'

const childFactoryCreator = (props) => child => React.cloneElement(child, props)

export const Transitions = ({ transition, duration, pageKey, children, ...props }) => (
  <TransitionGroup
    childFactory={childFactoryCreator({ classNames: transition, timeout: duration })}
    {...props}
  >
    <CSSTransition
      key={pageKey}
      classNames={transition}
      timeout={duration}
    >
      { children }
    </CSSTransition>
  </TransitionGroup>
)

Then on the router component:

withRouter(({ location }) => (
    // default is no transition. override if location has a state
    <Transitions pageKey={location.pathname} transition='' duration={0} {...location.state}>
        <Switch location={location}>
            <Route path='/home' component={Home} />
            <Route path='/next' component={Next} />
        </Switch>
    </Transitions>
))

I thought those that took a gander at the hackernoon article and are looking to animate transitions with React Router would, like me benefit from a revisit with React Transition Group v2:

https://github.com/gianlucacandiotti/react-router-transitions

This repo reconciles the diff and uses animate.js instead of a CSS child (which has greater React Native parity)

Hi @jasonintju , you could try something similar to the method discussed in https://github.com/ReactTraining/react-router/issues/5279#issuecomment-316877263 to specify a different class for the root page.

e.g.

const isRoot = location.pathname === '/';

and then

<CSSTransition key={location.pathname.split('/')[1]} timeout={500} classNames={isRoot ? 'left-to-right' : 'right-to-left'} mountOnEnter={true} unmountOnExit={true}>

This was very helpful @Horizon-Blue. Any thoughts on how one might achieve different transitions for each route?

Thanks @timdorr for the fast reply when you are so busy. However this article is not related to react-transition-group v2 - it is very different. Please don’t mind the thumbs down, I just don’t want others going there and wasting their time.