Route transitions with Reach Router

Note: This tutorial is for Reach Router. Users of React Router will want to use the React Router tutorial.

Route transitions in React are notoriously fiddly. With Pose and the accessibility-first Reach Router, they can be pretty simple.

We’re going first learn how to make a simple fade transition between two routes.

Then, as Pose has the ability to coordinate animations throughout the component tree, we’ll show how to animate each route differently, with content staggering in and out.

Here’s what we’ll be making:

Sandbox

We’ve created a CodeSandbox example preloaded with React Pose and Reach Router, for you to fork and follow along.

It’s set up in a standard way according to the Reach Router docs, so if you’re not already familiar with Reach Router, it’s worth reading the overview there.

Fade transition

So, let’s animate! The first animation we’ll add is a simple fade transition. When a user clicks a link, we want to fade the existing content out, and the new content in.

We’ll start by importing React Pose. We’re going to be using both posed and PoseGroup. So at the end of your imports, add:

import posed, { PoseGroup } from 'react-pose';

Now, let’s create a posed component with our two visual states, enter and exit. After the line above, add:

const RoutesContainer = posed.div({
  enter: { opacity: 1 },
  exit: { opacity: 0 }
});

We can now use this to wrap our Router component:

{({ location }) => (
  <RoutesContainer>
    <Router location={location}>
      {children}
    </Router>
  </RoutesContainer>
)}

To animate this component between the two enter and exit states, we can use the PoseGroup component.

PoseGroup tracks the entering and exiting of child components (as well as reordering), and will animate them in an out. Any components exiting will only be physically removed from the tree once they’ve finished their exit animation.

{({ location }) => (
  <PoseGroup>
    <RoutesContainer>
      <Router location={location}>
        {children}
      </Router>
    </RoutesContainer>
  </PoseGroup>
)}

Ah, but wait! If you try and click between pages, there’s still no animation. What’s up?

Without passing RoutesContainer a key, PoseGroup doesn’t know that it has a new child, so it can’t animate anything. Let’s use Reach Router’s location.key prop as our key:

<RoutesContainer key={location.key}>

Now, when you change routes, the content will fade in and out!

The two pieces of content currently fade on top of each other. By adding a small delay to the enter state, we can optionally separate the animations:

const RoutesContainer = posed.div({
  enter: { opacity: 1, delay: 300 },
  exit: { opacity: 0 }
});

Content transitions

With that animation in place, we can go a step further and animate the entering and exiting content.

First, add a beforeChildren: true property to the RoutesContainer enter pose. This will ensure that it finishes fading in before we animate any of its children:

const RoutesContainer = posed.div({
  enter: {
    opacity: 1,
    delay: 300,
    beforeChildren: true
  },
  exit: { opacity: 0 }
});

Let’s animate our first page. Open pages/about.js. You’ll see that we’ve pre-made two posed components, Container and P, and used those to markup the About component.

In the markup, the P components are children of Container, and Container is a child of RoutesContainer. So when RoutesContainer changes to enter and exit poses, these will flow through each of the posed components in turn, allowing us to animate them.

Add enter and exit poses to P:

const P = posed.p({
  enter: { x: 0, opacity: 1 },
  exit: { x: 50, opacity: 0 }
});

Now, when you enter and exit the About page, all the paragraphs will animate in and out. But they all animate in together. It’d be nice to stagger these animations instead.

That’s why we’ve made Container a posed component too. It’s not going to do animation itself, it’s just going to control the animation of its children with the staggerChildren property:

const Container = posed.div({
  enter: { staggerChildren: 50 }
});

Try entering and exiting the About page again. The paragraphs stagger in.

We can try the same trick on the Home page. Open pages/home.js and replace the ListContainer and Item posed components with this:

const ListContainer = posed.ul({
  enter: { staggerChildren: 50 },
  exit: { staggerChildren: 20, staggerDirection: -1 }
});

const Item = posed.li({
  enter: { y: 0, opacity: 1 },
  exit: { y: 50, opacity: 0 }
});

This time, ListContainer‘s exit pose has a new property, staggerDirection. Setting this to -1 reverses the stagger, so elements animate out from the bottom up.

Conclusion

We’ve learned how to use Pose with Reach Router to do a quick and simple fade transition, as well as animating across children to provide unique effects for every page.

We’ve also seen how posed components can be used not only to animate, but to sequence the animations of their children.