Technology Blog

Look deep into latest news and innovations happening in the Tech industry with our highly informational blog.

Dynamic transitions with react-router and react-transition-group

hkis
Let’s describe the problem first, we’ll discuss about the solution with some example code to cover it in this article.

React-router and react-transition-group are two widely used libraries that can be combined to create transitions between routes.

However, in react-transition-group, once a component is mounted, its exit animation is specified, not changeable. Thus, dealing with transitions depending of the next state (what we call dynamic transitions) is challenging with this library.

Dynamic transitions with react-router and react-transition-group

The exiting transition of state A does not depend only from state A (“dynamic transition”)

it is not easy to tweak it to create more sophisticated use cases such as dynamic transitions. In this article, we’ll explain how to do so thanks to react-router v4 and react-transition-group v2.

1. Understanding the simple example

If you are on your way developing transitions between pages of your react app, you might have already met this code snippet. (adapted with state A/B):

<TransitionGroup>
<CSSTransition key={location.key} classNames="fade" timeout={300}>
<Switch location={location}>
<Route exact path="/state-a" component={A} />
<Route exact path="/state-b" component={B} />
</Switch>
</CSSTransition>
</TransitionGroup>

Understanding why this piece of code allows a transition between two routes is not obvious. However, it is necessary to implement a more sophisticated use case such as dynamic transitions.

About TransitionGroup

When transitioning from state A to state B, location.key value changes (let’s say from A to B) so without a <TransitionGroup> wrapping <CSSTransition key={location.key}> , the <CSSTransition key='A'> would be unmounted and a new <CSSTransition key='B'> would be mounted (because react identifies elements thanks to key).

However, <TransitionGroup> tracks its children by key and when one of its children disappears, it keeps rendering it for the time of the transition. So during the time of the transition, the above TransitionGroup would render something similar to this:

<div>
<CSSTransition key='A' leaving>
<Switch location={location}>
<Route exact path="/state-a" component={A} />
<Route exact path="/state-b" component={B} />
</Switch>
</CSSTransition>
<CSSTransition key='B' entering>
<Switch location={location}>
<Route exact path="/state-a" component={A} />
<Route exact path="/state-b" component={B} />
</Switch>
</CSSTransition>
</div>

About Switch

You simply need to understand that when location.pathname is /state-a, this:

<Switch>
<Route exact path="/state-a" component={A} />
<Route exact path="/state-b" component={B} />
</Switch>

it renders:

<A />

Why you need to pass a location prop to the switch?

By default, a switch uses history.location to select the route to render. However you can provide a location prop to the switch that will override the default history.location value:

<TransitionGroup>
<CSSTransition key={location.key} classNames="fade" timeout={300}>
<Switch location={location}>
<Route exact path="/state-a" component={A} />
<Route exact path="/state-b" component={B} />
</Switch>
</CSSTransition>
</TransitionGroup>

So why this location (provided by withRouter or available within a route component) must be added as a prop to the switch in a transition use case?

history.location is a live object whereas the location provided by withRouter is immutable. Thus, without providing a location prop to the switch, the switch would always match the route according to the current location (the location of history.location). So during the transition (the current location is B), the <TransitionGroup> would render:

<div>
<CSSTransition key='A' leaving>
<B />
</CSSTransition>
<CSSTransition key='B' entering>
<B />
</CSSTransition>
</div>

However, if you pass a location to the switch, the switch will use this prop instead of history.location and since location is immutable, the previous <CSSTransition> received the previous location and the new <CSSTransition> receives the new location.

<div>
<CSSTransition key='A' leaving>
<A />
</CSSTransition>
<CSSTransition key='B' entering>
<B />
</CSSTransition>
</div>

Thereby the leaving <CSSTransition> will still render an old route even if a new location has been pushed to the history.

2. Dealing with dynamic transitions

Dealing with dynamic transitions is not straight forward. An issue on react-transition-group is open to consider this problem.

As explained in the issue:

once a component is mounted, its exit animation is specified, not changeable.

Indeed, in this code snippet:

<TransitionGroup>
<CSSTransition key={location.key} classNames="fade" timeout={300}>
<Switch location={location}>
<Route exact path="/state-a" component={A} />
<Route exact path="/state-b" component={B} />
</Switch>
</CSSTransition>
</TransitionGroup>

only the current (entering) child is accessible. The exiting one has already been removed. It is only living within the <TransitionGroup> state.

Fortunately the <TransitionGroup> component can receive a childFactory.

So the childFactory prop makes it possible to specify the leaving transition of a component after rendering it (and thus solves the problem of dynamic transitions)

<TransitionGroup
childFactory={child => React.cloneElement(
child,
{classNames: "newTransition", timeout: newTimeout}
)}
>
<CSSTransition key={location.key}>
<Switch location={location}>
<Route exact path="/state-a" component={A} />
<Route exact path="/state-b" component={B} />
</Switch>
</CSSTransition>
</TransitionGroup>

In the above code snippet, the previous <CSSTransition> will be updated with the new transition class name and timeout.

A possible implementation of dynamic transitions

The question is now: how do you give the right classNames value according to the state transition?

A possible solution is to use the location state.

Here is how you could do:

// state-a.js
export default (props) => (
<div>
<button
onClick={() => {
history.push({
pathname: '/state-b',
state: {transition: 'fade', duration: 300}
})
}}
>Go to state B</button>
<button
onClick={() => {
history.push({
pathname: '/state-c',
state: {transition: 'slide', duration: 500}
})
}}
>Go to state C</button>
</div>
)

In the routes definition file:

<TransitionGroup
childFactory={child => React.cloneElement(
child,
{
classNames: location.state.transition,
timeout: location.state.duration
}
)}
>
<CSSTransition key={location.key}>
<Switch location={location}>
<Route exact path="/state-a" component={A} />
<Route exact path="/state-b" component={B} />
</Switch>
</CSSTransition>
</TransitionGroup>

Now you should get 2 different transitions from the same exiting state. This is what we were trying to solve.

For more Information and to build web app using React.js, Hire React.js Developer from us as we provide you high quality services by utilizing all the latest tools and advanced technology. E-mail us any clock at – hello@hkinfosoft.com or Skype us: “hkinfosoft“.

To develop custom web app using React.js, please visit our technology page.

Content Source:

  1. medium.com