This post is based on React Router v5.
What's the issue?
React Router is a great library for SPA navigation, but it has one small problem when we need a transition/animation effect while we navigation between pages - React Router, by default, changes to a new route instantly, so the element does not have time to play the transition effect.
What we need to do?
- Prevent component from unmounting before the 'Out' animation is completed.
- Switch component when the 'In' animation is start.
So the transition stages will be:
Link clicked -> prevent the route switching -> 'Out' animation start -> 'Out' animation end -> switch to new route -> 'In' animation start -> 'In' animation end - > done
Let's start!
First, let's create the App component:
export default function App() {
return (
<BrowserRouter>
<div className={`App`}>
<nav>
<Link to='/'>Home</Link>
<Link to='/other'>Other</Link>
</nav>
<Content />
</div>
</BrowserRouter>
);
}
Then, the Content component:
function Content() {
return (
<div>
<Switch>
<Route path="/other">
<section>other</section>
</Route>
<Route path="/">
<section>home</section>
</Route>
</Switch>
</div>
);
}
Now, we need to stop route from switching. By default, the<Switch/>
will use the current url for matching the route, but we can stop it from doing that by assigning it a Location
.
<Switch location={SomeLocation}>
We will need a state to keep the current location before 'Out' animation finish, and we can assign the current location as the default value. we can use useLocation
to get the current location.
...
const location = useLocation();
const [displayLocation, setDisplayLocation] = useState(location);
...
<Switch location={displayLocation}>
Now, if you click the Link
, you will notice that even the URL is changed, the content stay the same.
Next, we need add a state for controlling the stage of the transition.
const [transitionStage, setTransistionStage] = useState("fadeIn");
Then, we can use useEffect
to check if location is changed, and start the 'fadeOut'.
useEffect(() => {
if (location !== displayLocation) setTransistionStage("fadeOut");
}, [location]);
Finally, we need a way to update the stage and location when animation is over. For that we can use the onAnimationEnd
event.
function Content() {
...
return (
<div
className={`${transitionStage}`}
onAnimationEnd={() => {
if (transitionStage === "fadeOut") {
setTransistionStage("fadeIn");
setDisplayLocation(location);
}
}}
>
...
</div>
);
}
And, Here is the demo and the finished code:
import { createContext, useContext, useEffect, useRef, useState } from "react";
import {
BrowserRouter,
Redirect,
Route,
Switch,
Link,
useLocation
} from "react-router-dom";
import "./styles.css";
export default function App() {
return (
<BrowserRouter>
<div className={`App`}>
<nav>
<Link to="/">Home</Link>
<Link to="/other">Other</Link>
</nav>
<Content />
</div>
</BrowserRouter>
);
}
function Content() {
const location = useLocation();
const [displayLocation, setDisplayLocation] = useState(location);
const [transitionStage, setTransistionStage] = useState("fadeIn");
useEffect(() => {
if (location !== displayLocation) setTransistionStage("fadeOut");
}, [location]);
return (
<div
className={`${transitionStage}`}
onAnimationEnd={() => {
if (transitionStage === "fadeOut") {
setTransistionStage("fadeIn");
setDisplayLocation(location);
}
}}
>
<Switch location={displayLocation}>
<Route path="/other">
<section>other</section>
</Route>
<Route path="/">
<section>home</section>
</Route>
</Switch>
</div>
);
}
Thank you all!
Top comments (4)
Thanks! It really helps!
Is it possible to do this animation with react-router v6?
It's possible :))
Now I will try to write a post😂
This is by far the simplest most elegant implementation of route animation