Martin Haagensli

@martinhaagensli

Animated page transitions with React Router 4, ReactTransitionGroup and Animated

In this article I’ll show you how to animate your page transitions using lifecycle methods from ReactTransitionGroup and the Animated library.

Here’s a demo of some simple transitions using this pattern (you can also check out the live version at http://animate.mhaagens.me);

View it live at http://animate.mhaagens.me

All right, that’s it for the quick demo! 
Let’s see how we can set up some simple route animations!

React setup

Let’s install React with the fantastic Create React App, a simple way to get a React project up and running.

If you haven’t installed Create React App already (if you have, skip this step);

npm install -g create-react-app

Then let’s create our project;

create-react-app animatedroutes && cd animatedroutes

Then let’s install our packages for routes and animation;

yarn add react-router-dom animated react-transition-group

Now open the project in your favourite editor and run;

npm start

Adding React Router

Open your src/index.js file and add BrowserRouter from React

import React from "react";
import ReactDOM from "react-dom";
import { BrowserRouter } from "react-router-dom";
import App from "./App";
import registerServiceWorker from "./registerServiceWorker";
import "./index.css";
ReactDOM.render(
<BrowserRouter>
<App />
</BrowserRouter>,
document.getElementById("root")
);
registerServiceWorker();

Then let’s create two components we can render;

First src/Home.js;

import React, { Component } from "react";
export default class Home extends Component {
render() {
return (
<div className="page">
<h1>Home</h1>
<p>Hello from the home page!</p>
</div>
)
}
}

Then src/Subpage.js ;

import React, { Component } from "react";
export default class Subpage extends Component {
render() {
return (
<div className="page">
<h1>Subpage</h1>
<p>Hello from a sub page!</p>
</div>
)
}
}

Then open src/App.js and change it to this;

import React, { Component } from 'react';
import { Route, Link } from "react-router-dom";
import Home from "./Home";
import Subpage from "./Subpage";
class App extends Component {
render() {
return (
<div className="App">
<div className="TopBar">
<Link to="/">Home</Link>
<Link to="/subpage">Subpage</Link>
</div>
<Route exact path="/" component={Home} />
<Route exact path="/subpage" component={Subpage} />
</div>
);
}
}
export default App;

Then remove everything in src/App.css and paste the following in src/index.css ;

html,
body,
#root {
height: 100%;
width: 100%;
}
body {
margin: 0;
padding: 0;
font-family: sans-serif;
}
.App {
position: relative;
display: flex;
flex-flow: column;
}
.TopBar {
position: fixed;
top: 0;
left: 0;
display: flex;
flex-flow: row nowrap;
align-items: center;
width: 100%;
height: 62px;
padding: 0 24px;
}
.TopBar a {
margin-right: 18px;
text-decoration: none;
}
.animated-page-wrapper {
position: absolute;
top: 62px;
left: 0;
width: 100%;
height: 100%;
}
.page {
padding: 0 24px;
}

Okay. You should now be able to navigate between two routes, the homepage and a subpage.

Adding TransitionGroup

Now we’re ready to start animating the routes.
There’s a few things we need to change and add to make this work;

Instead of rendering our routes the normal way, were now going to use the Route render-method to render our component and wrap them in a <TransitionGroup />.

First import TransitionGroup in your src/App.js component like this;

import TransitionGroup from "react-transition-group/TransitionGroup";

Then we have to add a special function for Transition Group to render a single child. Above class App extends ... in src/App.js , add this function;

const firstChild = props => {
const childrenArray = React.Children.toArray(props.children);
return childrenArray[0] || null;
};

Then remove your routes and replace them with this;

<Route
exact
path="/"
children={({ match, ...rest }) => (
<TransitionGroup component={firstChild}>
{match && <Home {...rest} />}
</TransitionGroup>
)}/>
<Route
path="/subpage"
children={({ match, ...rest }) => (
<TransitionGroup component={firstChild}>
{match && <Subpage {...rest} />}
</TransitionGroup>
)}/>

You now have access to new lifecycle methods such as componentWillAppear() , componentWillEnter() and componentWillLeave() .

Let’s use them to make a Higher Order Component which animates our routes! Now the real fun begins!

Creating our Animated Wrapper and animating with Animated (can I say animated again..?)

Create src/AnimatedWrapper.js and paste in this;

import React, { Component } from "react";
import * as Animated from "animated/lib/targets/react-dom";
const AnimatedWrapper = WrappedComponent => class AnimatedWrapper
extends Component {
constructor(props) {
super(props);
this.state = {
animate: new Animated.Value(0)
};
}
render() {
return (
<Animated.div className="animated-page-wrapper">
<WrappedComponent {...this.props} />
</Animated.div>
);
}
};
export default AnimatedWrapper;

There’s a lot going on here, so I’ll explain a bit.

Were making a component to wrap our route component. It will receive the lifecycle methods from TransitionGroup, which we can use for animation.
We also use Animated to create a value we can use to animate different style properties of the div that wraps our child component.

Let’s add some lifecycle methods to animate our component, and an Animated.template`` to render and/or interpolate our state animation value.

Change src/AnimatedWrapper.js to this;

import React, { Component } from "react";
import * as Animated from "animated/lib/targets/react-dom";
const AnimatedWrapper = WrappedComponent => class AnimatedWrapper
extends Component {
constructor(props) {
super(props);
this.state = {
animate: new Animated.Value(0)
};
}
componentWillAppear(cb) {
Animated.spring(this.state.animate, { toValue: 1 }).start();
cb();
}
componentWillEnter(cb) {
setTimeout(
() => Animated.spring(this.state.animate, { toValue: 1 }).start(),
250
);
cb();
}
componentWillLeave(cb) {
Animated.spring(this.state.animate, { toValue: 0 }).start();
setTimeout(() => cb(), 175);
}
render() {
const style = {
opacity: Animated.template`${this.state.animate}`,
transform: Animated.template`
translate3d(0,${this.state.animate.interpolate({
inputRange: [0, 1],
outputRange: ["12px", "0px"]
})},0)
`
};
return (
<Animated.div style={style} className="animated-page-wrapper">
<WrappedComponent {...this.props} />
</Animated.div>
);
}
};
export default AnimatedWrapper;

Then we have to import it in each of our route components and wrap them like this;

Change src/Home.js to this;

import React, { Component } from "react";
import AnimatedWrapper from "./AnimatedWrapper";
class HomeComponent extends Component {
render() {
return (
<div className="page">
<h1>Home</h1>
<p>Hello from the home page!</p>
</div>
)
}
}
const Home = AnimatedWrapper(HomeComponent);
export default Home;

and src/Subpage.js to this;

import React, { Component } from "react";
import AnimatedWrapper from "./AnimatedWrapper";
class SubpageComponent extends Component {
render() {
return (
<div className="page">
<h1>Subpage</h1>
<p>Hello from a sub page!</p>
</div>
)
}
}
const Subpage = AnimatedWrapper(SubpageComponent);
export default Subpage;

That’s it! Your routes should now be animating in and out!

Further learning

I recommend reading through the Animated docs (they’re pretty sparse at the moment, the Animated.template`` function we’re using isn’t even documented outside of Github-issues.
You can look at the docs here; http://animatedjs.github.io/interactive-docs/

You can also download the example project living at http://animate.mhaagens.me/
here;
https://github.com/mhaagens/animated_routes_react

Follow me here on Medium or on Twitter for more React tutorials; https://twitter.com/mhaagens

Hacker Noon is how hackers start their afternoons. We’re a part of the @AMI family. We are now accepting submissions and happy to discuss advertising & sponsorship opportunities.
If you enjoyed this story, we recommend reading our latest tech stories and trending tech stories. Until next time, don’t take the realities of the world for granted!

More by Martin Haagensli

Topics of interest

More Related Stories