Animated page transitions with React Router 4, ReactTransitionGroup and Animated

Written by martinhaagensli | Published 2017/06/25
Tech Story Tags: react | react-router-4 | route-transitions | animation

TLDRvia the TL;DR App

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;

<Routeexactpath="/"children={({ match, ...rest }) => (<TransitionGroup component={firstChild}>{match && <Home {...rest} />}</TransitionGroup>)}/><Routepath="/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 AnimatedWrapperextends 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 AnimatedWrapperextends 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


Published by HackerNoon on 2017/06/25