Search code examples
reactjsreact-routerreact-router-domreact-transition-group

Animated react routes not working with <Switch> component


I am trying to build a simple router for my React application, which would consist of a few routes generated from an array of objects and a static 404 route. The requirement is that transitions between every route must be animated.

I am using react-router for the browser and react-transition-group.

I want to achieve something like this (stripped-down, incomplete pseudo-code):

const routes = [
    { path: "/", component: Home },
    { path: "/about", component: About },
    { path: "/contact", component: Contact }
];

const createRoute = (route) => {
    return (
        <CSSTransition className="page" timeout={300}>
            <Route path={route.path} exact component={route.component} />
        </CSSTransition>
    );
}

<Router>
    {routes.map(createRoute)}
    <CSSTransition className="page" timeout={300}>
        <Route component={PageNotFound} />
    </CSSTransition>
</Router>

A full version of this code can be found on this Codesandbox:

https://codesandbox.io/s/react-router-switch-csstransition-catch-all-route-bug-forked-qzt9g

I need to use the <Switch> component to prevent the 404 route from showing in all the other routes of the application, but as soon as I add the <Switch>, the animations stop working.

In the example above, you will see that the animations don't work when used with <Switch>, despite following the guides from official docs of both react-router and react-transition-group.

However, they work perfectly without the use of the <Switch>, but then of course I end up with 404 route showing all the time.

Expected result:

  • animated transitions between all routes, those dynamically created as well as the static 404 page

Actual result:

  • no animations at all or animations with 404 route always showing

I have spent the entire day trying to find a solution to the problem that I encountered. Unfortunately I can't seem to find anything that would remotely help me fix the issue I'm facing, and I've searched on Google, Stack Overflow, Medium and finally I'm back here hoping someone can help me out please.


Solution

  • In order to have the animation working with the Switch component, you have to pass the right location with withRouter to the CSSTransition, coupled with TransitionGroup component. I've modified you sandbox code with the following working solution:

    import React from "react";
    import ReactDOM from "react-dom";
    import { CSSTransition, TransitionGroup } from "react-transition-group";
    import {
      BrowserRouter as Router,
      Route,
      Switch,
      Link,
      withRouter
    } from "react-router-dom";
    
    import "./styles.css";
    
    const rootElement = document.getElementById("root");
    
    const components = {
      Home: () => <div>Home page</div>,
      About: () => <div>About the company</div>,
      Contact: () => <div>Contact us</div>,
      NotFound: () => <div>404</div>,
      Menu: ({ links, setIsSwitch }) => (
        <div className="menu">
          {links.map(({ path, component }, key) => (
            <Link key={key} to={path}>
              {component}
            </Link>
          ))}
          <Link to="/404">404</Link>
        </div>
      )
    };
    
    const routes = [
      { path: "/", component: "Home" },
      { path: "/about", component: "About" },
      { path: "/contact", component: "Contact" }
    ];
    
    const createRoutes = (routes) =>
      routes.map(({ component, path }) => {
        const Component = components[component];
        const nodeRef = React.createRef();
    
        return (
          <Route key={path} path={path} exact>
            {({ match }) => {
              return (
                <div ref={nodeRef} className="page">
                  <Component />
                </div>
              );
            }}
          </Route>
        );
      });
    
    const AnimatedSwitch = withRouter(({ location }) => (
      <TransitionGroup>
        <CSSTransition
          key={location.key}
          timeout={500}
          classNames="page"
          unmountOnExit
        >
          <Switch location={location}>{createRoutes(routes)}</Switch>
        </CSSTransition>
      </TransitionGroup>
    ));
    
    ReactDOM.render(
      <React.StrictMode>
        <Router>
          <components.Menu links={routes} />
          <AnimatedSwitch />
        </Router>
      </React.StrictMode>,
      rootElement
    );
    

    This article explains in detail the reasoning behind it: https://latteandcode.medium.com/react-how-to-animate-transitions-between-react-router-routes-7f9cb7f5636a.

    By the way withRouter is deprecated in react-router v6. So you should implement that hook in your own.

    import { useHistory } from 'react-router-dom';
    
    export const withRouter = (Component) => {
      const Wrapper = (props) => {
        const history = useHistory();
        
        return (
          <Component
            history={history}
            {...props}
            />
        );
      };
      
      return Wrapper;
    };
    

    See Deprecated issue discussion on GitHub: https://github.com/remix-run/react-router/issues/7156.