DEV Community

Anxiny
Anxiny

Posted on • Edited on

React-Router: Animated Transitions DIY

This post is based on React Router v5.

What's the issue?

React Router is a great library for SPA navigation, but it has one small problem when we need a transition/animation effect while we navigation between pages - React Router, by default, changes to a new route instantly, so the element does not have time to play the transition effect.

What we need to do?

  • Prevent component from unmounting before the 'Out' animation is completed.
  • Switch component when the 'In' animation is start.

So the transition stages will be:

Link clicked -> prevent the route switching -> 'Out' animation start -> 'Out' animation end -> switch to new route -> 'In' animation start -> 'In' animation end - > done

Let's start!

First, let's create the App component:

export default function App() {
  return (
    <BrowserRouter>
      <div className={`App`}>
        <nav>
          <Link to='/'>Home</Link>
          <Link to='/other'>Other</Link>
        </nav>
        <Content />
      </div>
    </BrowserRouter>
  );
}

Enter fullscreen mode Exit fullscreen mode

Then, the Content component:

function Content() {
  return (
    <div>
      <Switch>
        <Route path="/other">
          <section>other</section>
        </Route>
        <Route path="/">
          <section>home</section>
        </Route>
      </Switch>
    </div>
  );
}

Enter fullscreen mode Exit fullscreen mode

Now, we need to stop route from switching. By default, the<Switch/> will use the current url for matching the route, but we can stop it from doing that by assigning it a Location.

  <Switch location={SomeLocation}>
Enter fullscreen mode Exit fullscreen mode

We will need a state to keep the current location before 'Out' animation finish, and we can assign the current location as the default value. we can use useLocation to get the current location.

  ...
  const location = useLocation();
  const [displayLocation, setDisplayLocation] = useState(location);
  ...
  <Switch location={displayLocation}>
Enter fullscreen mode Exit fullscreen mode

Now, if you click the Link, you will notice that even the URL is changed, the content stay the same.

Next, we need add a state for controlling the stage of the transition.

  const [transitionStage, setTransistionStage] = useState("fadeIn");
Enter fullscreen mode Exit fullscreen mode

Then, we can use useEffect to check if location is changed, and start the 'fadeOut'.

  useEffect(() => {
    if (location !== displayLocation) setTransistionStage("fadeOut");
  }, [location]);
Enter fullscreen mode Exit fullscreen mode

Finally, we need a way to update the stage and location when animation is over. For that we can use the onAnimationEnd event.

function Content() {
  ...
  return (
    <div
      className={`${transitionStage}`}
      onAnimationEnd={() => {
        if (transitionStage === "fadeOut") {
          setTransistionStage("fadeIn");
          setDisplayLocation(location);
        }
      }}
    >
    ...
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

And, Here is the demo and the finished code:

import { createContext, useContext, useEffect, useRef, useState } from "react";
import {
  BrowserRouter,
  Redirect,
  Route,
  Switch,
  Link,
  useLocation
} from "react-router-dom";
import "./styles.css";

export default function App() {
  return (
    <BrowserRouter>
      <div className={`App`}>
        <nav>
          <Link to="/">Home</Link>
          <Link to="/other">Other</Link>
        </nav>
        <Content />
      </div>
    </BrowserRouter>
  );
}

function Content() {
  const location = useLocation();
  const [displayLocation, setDisplayLocation] = useState(location);
  const [transitionStage, setTransistionStage] = useState("fadeIn");

  useEffect(() => {
    if (location !== displayLocation) setTransistionStage("fadeOut");
  }, [location]);

  return (
    <div
      className={`${transitionStage}`}
      onAnimationEnd={() => {
        if (transitionStage === "fadeOut") {
          setTransistionStage("fadeIn");
          setDisplayLocation(location);
        }
      }}
    >
      <Switch location={displayLocation}>
        <Route path="/other">
          <section>other</section>
        </Route>
        <Route path="/">
          <section>home</section>
        </Route>
      </Switch>
    </div>
  );
}

Enter fullscreen mode Exit fullscreen mode

Thank you all!

Top comments (4)

Collapse
 
sixgramwater profile image
sixgramwater

Thanks! It really helps!

Collapse
 
fazliddin04 profile image
Fazliddin

Is it possible to do this animation with react-router v6?

Collapse
 
fazliddin04 profile image
Fazliddin • Edited

It's possible :))
Now I will try to write a post😂

Collapse
 
gwitchr profile image
Luis Casillas

This is by far the simplest most elegant implementation of route animation