React. Cómo animar las transiciones entre rutas de React Router
Aprende a crear animaciones al navegar entre rutas empleando React Router
English version: https://medium.com/@ger86/react-how-to-animate-transitions-between-react-router-routes-7f9cb7f5636a
En este artículo quiero hablaros acerca de cómo podemos animar las transiciones entre las rutas de nuestra aplicación cuando trabajamos con React Router, una de las principales librerías que tenemos dentro del ecosistema de React para definir un sistema de enrutamiento.
La idea será emplear la librería React Transition Group de cara a definir estas animaciones (las cuales crearemos empleando CSS básico) lo cual nos ayudará a familiarizarnos con sus conceptos básicos a la vez que profundizamos en algunas características muy interesantes de React Router.
Así que, sin entretenernos más, ¡vamos a ello!
Animación básica con React Transition Group
Si no habéis trabajado nunca con esta librería creo que lo más interesante para interiorizar su funcionamiento será ver un ejemplo sencillo. Para ello, partiré de un artículo anterior en el que explicaba la forma en que podemos usar la API React Portals para crear una ventana modal en nuestra aplicación.
En este artículo creé un repositorio con el código final que será el que emplearemos como punto de partida para añadir una animación a la ventana modal cuando se muestra.
Así que primero instalaremos la librería React Transition Group:
yarn add react-transition-group
Si habéis visto este repositorio, el código encargado de crear el portal asociado a nuestra modal es el siguiente:

La idea ahora será añadir una transición al <div class="modal-dialog>
de modo que la ventana modal se muestre de arriba a abajo.
Así que lo que haremos será envolverlo empleando el componente CSSTransition que nos proporciona la librería React Transition Group.

Veamos lo que significa cada uno de los atributos que hemos añadido:
- appear indica que, aunque el componente ya se esté mostrando cuando se monta el componente
CSSTransition
, la animación también tenga lugar, ya que el comportamiento por defecto es no ejecutarla si el componente a animar ya se está mostrando. En nuestro ejemplo lo ponemos atrue
para forzar este comportamiento. - in nos permite mostrar u ocultar el componente a animar. En nuestro ejemplo lo ponemos a
true
ya que la modal ya se está mostrando una vez que el portal se ha creado. - classNames nos permite definir el prefijo de las clases que el componente CSSTransition añadirá al componente a animar. Son las siguientes:
.modal-transition-enter {
}.modal-transition-enter-active {
}.modal-transition-exit {}.modal-transition-exit-active {}.modal-transition-appear {}.modal-transition-appear-active {}
- timeout, para definir la duración de la transición en milisegundos.
Hecho esto, lo que haremos será definir el CSS asociado a la transición partiendo de las clases que CSSTransition. En nuestro caso será el siguiente:

En nuestro caso el código CSS es muy sencillo. Básicamente definimos la clase modal-transition-appear
para definir la duración de la transición y que inicialmente la modal se desplace -100% hacia arriba de cara a que, cuando el componente CSSTransition añada la clase modal-transition-appear-active
la modal se desplace hacia abajo mediante transform: translateY(0)
.
Con esto ya hemos conseguido lo que queríamos 🎉🎉🎉:
Animando transiciones entre rutas de React Router
Una vez que ya entendemos el funcionamiento básico del componente CSSTransition lo que haremos será aplicar una lógica similar para animar las transiciones entre las distintas rutas de nuestra aplicación cuando trabajamos con React Router.
Pero antes de comenzar, quiero contar algunas cosa interesantes sobre cómo funciona la librería React Transition Group.
💡 React Transition Group
Además del componente CSSTransition, la librería React Transition Group nos proporciona el componente TransitionGroup que nos permite animar una lista de elementos.
De este modo, cuando envolvemos una lista de elementos que queremos animar con el componente <TransitionGroup>
, éste lleva el seguimiento de cada elemento en la lista mediante la propiedad key
. Es decir, <TransitionGroup>
posee un estado interno donde almacena quiénes son sus componentes hijos, de modo que cuando se produce un cambio, puede determinar qué elementos están entrando y cuáles saliendo y renderearlos con sus respectivas clases ( *-appear
, *-enter
, y *-exit
del ejemplo anterior) para animarlos.
Por ejemplo, si inicialmente dentro de un <TransitionGroup>
tenemos lo siguiente:
<TransitionGroup>
<CSSTransition key="first" ...>
<Item/>
</CSSTransition>
</TransitionGroup>
y de repente cambiamos a:
<TransitionGroup>
<CSSTransition key="second" ...>
<Item/>
</CSSTransition>
</TransitionGroup>
Habrá un momento en el que <TransitionGroup>
estará rendereando lo siguiente:
<TransitionGroup> // saliendo
<CSSTransition key="first" ...>
<Item/>
</CSSTransition> // entrando
<CSSTransition key="second" ...>
<Item/>
</CSSTransition>
</TransitionGroup>
Permitiéndonos realizar animaciones entre varios elementos.
💡 Conceptos básicos para animar React Router
Como sabréis, los componentes Route
y Switch
de React Router emplean una propiedad llamada location
para determinar qué ruta de las que hemos “matchea” con la ruta actual del navegador. Esta propiedad es extraída por defecto del objeto context
de React Router y es un objeto “vivo”, es decir, que se va actualizando a medida que el usuario navega.
Sin embargo, este comportamiento, como veremos más adelante, tiene un problema cuando tenemos rutas que “entran” y rutas que “salen”, ya que éstas últimas ya no “matchearán” y por tanto no se pintarán. Por suerte existen formas de saltarnos esta limitación.
Lo primero que hay que tener en cuenta es que el elemento que animaremos será el componente <Switch>
de React Router y no el propio componente Route
en sí mismo.
Es decir, no tendremos ésto:
<Switch>
<TransitionGroup>
<CSSTransition>
<Route path='/first'><FirstComponent /></Route>
<Route path='/second'><SecondComponent /></Route>
...
</CSSTransition>
</TransitionGroup>
</Switch>
sino ésto:
<TransitionGroup>
<CSSTransition>
<Switch>
<Route path='/first'><FirstComponent /></Route>
<Route path='/second'><SecondComponent /></Route>
</Switch>
</CSSTransition>
</TransitionGroup>
De este modo, siguiendo la lógica del componente <TransitionGroup>
que os comentaba antes, durante la animación el DOM tendrá lo siguiente:
<TransitionGroup> // saliendo
<CSSTransition>
<Switch>
<Route path='/first'><FirstComponent /></Route>
<Route path='/second'><SecondComponent /></Route>
</Switch>
</CSSTransition> // entrando
<CSSTransition>
<Switch>
<Route path='/first'><FirstComponent /></Route>
<Route path='/second'><SecondComponent /></Route>
</Switch>
</CSSTransition>
</TransitionGroup>
🔛 Creando una transición básica en React Router
Con todos los conceptos anteriores ya podemos definir un componente llamado <AnimatedSwitch>
que nos permita realizar una animación sencilla entre nuestras rutas:
const AnimatedSwitch = withRouter(({ location }) => (
<TransitionGroup>
<CSSTransition
key={location.key}
classNames="slide"
timeout={1000}
>
<Switch>
<Route path='/first' component={First} />
<Route path='/second' component={Second} />
</Switch>
</CSSTransition>
</TransitionGroup>
));
Tal y como comentábamos antes, estamos definiendo una propiedad key
para el componente CSSTransition
de cara a que el componente TransitionGroup
pueda llevar el seguimiento de que Switch
entra y de cual sale.
Para definir esta key
emplearemos el objeto location
de React Router, al cual podemos acceder empleando el HOC withRouter
.
Veamos lo que hemos conseguido:
Vaya… No parece que funcione del todo bien. La animación la hemos conseguido pero la ruta que sale es la misma que a la que nos dirigimos en vez del comportamiento que cabría esperar
¿Por qué es esto?
Bien, esto se debe a que por defecto <Switch>
está trabajando con el objeto location
de contexto de React Router, que, si recordáis lo dicho antes, es un objeto vivo que cambia a medida que el usuario navega.
De modo que, si navegamos por ejemplo de /
a /first
lo que sucederá es lo siguiente:
<TransitionGroup> // saliendo
<CSSTransition> // El Switch que sale está cogiendo el objeto location
// del contexto, por tanto
// location.path = '/first'
<Switch>
<Route path='/'><HomeComponent /></Route>
<Route path='/first'><FirstComponent /></Route>
<Route path='/second'><SecondComponent /></Route>
</Switch>
</CSSTransition> // entrando
<CSSTransition> // El Switch que entra también está cogiendo el objeto location
// del contexto, por tanto también
// location.path = '/first'
<Switch>
<Route path='/'><HomeComponent /></Route>
<Route path='/first'><FirstComponent /></Route>
<Route path='/second'><SecondComponent /></Route>
</Switch>
</CSSTransition>
</TransitionGroup>
¿Cómo podemos forzar a que el Switch que sale coja el objeto location “anterior” mientras que el que entra coja el objeto location con el path hacia el que nos dirigimos?
Pues de una forma mucho más fácil de la que podríamos pensar, ya que el componente Switch
admite que le pasemos una propiedad location
con el objeto de modo que en vez de usar el objeto vivo location
procedente del contexto coja el objeto que le estamos pasando. De este modo, podemos reescribir nuestro componente AnimatedSwitch
añadiendo lo siguiente:
const AnimatedSwitch = withRouter(({ location }) => (
<TransitionGroup>
<CSSTransition
key={location.key}
classNames="slide"
timeout={1000}
>
<Switch location={location}>
<Route path='/first' component={First} />
<Route path='/second' component={Second} />
</Switch>
</CSSTransition>
</TransitionGroup>
));
Es decir, al componente Switch
le estamos pasando el objeto inmutable location
procedente del HOC withRouter
, de modo que cuando se produzca la transición de la ruta /
a la ruta /first
lo que se rendeará dentro del componente TransitionGroup
será lo siguiente:
<TransitionGroup> // saliendo
<CSSTransition> // El Switch que sale está cogiendo el objeto location
// procedente del HOC withRouter, el cual es inmutable y por
// tanto, location.path = '/'
<Switch>
<Route path='/'><HomeComponent /></Route>
<Route path='/first'><FirstComponent /></Route>
<Route path='/second'><SecondComponent /></Route>
</Switch>
</CSSTransition>// entrando
<CSSTransition> // El Switch que entra también está cogiendo el objeto location
// del HOC withRouter, que ahora contendrá la información
// actualizada con la ruta hacia la que nos dirigimos, es decir
// location.path = 'first'
<Switch>
<Route path='/'><HomeComponent /></Route>
<Route path='/first'><FirstComponent /></Route>
<Route path='/second'><SecondComponent /></Route>
</Switch>
</CSSTransition>
</TransitionGroup>
Y con esto ya tendremos nuestra flamante animación funcionando correctamente:
Conclusiones
Como veis, crear una animación para las transiciones entre nuestras rutas es relativamente sencillo si conocemos la forma en que funcionan las librerías React Router y React Transition Group.
Sin embargo, nuestro ejemplo tiene una limitación: ¿verdad que estaría bien que cuando vamos hacia la derecha la animación fuera en un sentido y cuando volvemos la animación fuera en el contrario?
Bien, este comportamiento requiere una dosis extra de esfuerzo (no mucho, prometido 😊😊😊) que os contaré en el siguiente artículo.
Espero que este os haya servido para familiarizaros con la librería React Transition Group y que os sirva para animaros a darle un poco de vida a vuestras aplicaciones hechas con React.
Si queréis leer la segunda parte de este artículo podéis hacerlo desde el siguiente enlace:
Referencias
¿Quieres recibir más artículos como este?
Si te ha gustado este artículo te animo a que te suscribas a la newsletter que envío cada domingo con publicaciones similares a esta y más contenido recomendado: 👇👇👇