Thanks to Anxin.Y for the article about making a transition DIY through animation on React-router v5.
Now, I will try to make it with v6.
So, 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>
<Routes>
<Route path="/" element={<section>Home</section>} />
<Route path="/other" element={<section>Other</section>} />
</Routes>
</div>
);
}
Now, we need to stop route from switching. By default, the <Routes />
(in v5, <Switch />
) will use the current url for matching the route, but we can stop it from doing that by assigning it a Location.
<Routes location={displayLocation}>
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);
...
<Routes 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, displayLocation]);
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>
);
}
Before completion, You must add these to your CSS:
.fadeIn {
animation: 0.5s fadeIn forwards;
}
.fadeOut {
animation: 0.5s fadeOut forwards;
}
@keyframes fadeIn {
from {
opacity: 0;
transform: translate(-20px, 0);
}
to {
opacity: 1;
transform: translate(0px, 0px);
}
}
@keyframes fadeOut {
from {
opacity: 1;
transform: translate(0px, 0px);
}
to {
transform: translate(-20px, 0);
opacity: 0;
}
}
And, Here is the demo and the finished code:
import { useState, useEffect } from "react";
import {
BrowserRouter,
Link,
useLocation,
Route,
Routes
} 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, displayLocation]);
return (
<div
className={`${transitionStage}`}
onAnimationEnd={() => {
if (transitionStage === "fadeOut") {
setTransistionStage("fadeIn");
setDisplayLocation(location);
}
}}
>
<Routes location={displayLocation}>
<Route path="/" element={<section>Home</section>} />
<Route path="/other" element={<section>Other</section>} />
</Routes>
</div>
);
}
Thank you AnxinYang!
Top comments (12)
If i may, i was inspired by ur post and i tried to make it more flexible...
MainContent.js :
Styles for animations
Regards,
Fred, Brittany - France
YOU ARE AMAIZING! >.<
An issue here is that you are only displaying one view at a time.
What happens if you want to animate a route, mobile/native style, where one view slides in over another?
I am on something close here. The new React Router 6 has
useOutlet
where it will give you the current element in that outlet that that you are rendering.You can store that element stack in a
ref
and then when the navigation changes, use that stashed ref (which will be the old outlet results as the outlet will be updated) and then you render that under your new outlet with your animation triggering over.Obviously, if you have nested outlets and a mixture of tabs/menus/stacks your back-navigation will get tricky. Still, you it should be on the right direction.
Thank you!!! This is exactly what I needed, a DYI approach. Found some tutorials, but they needed Framer Motion or another libraries... Learned something new today π
Thank you! Love how you included a working code sandbox. It's very useable!
can you update this for the v6.4 + syntex ?
How can I do that with nested routes?
I like it!!!
Thanks for this article! With what I've learnt from this article I was able to create page transitions with GSAP.
This is aweesome, thanks. Just I have the same code as you but i get an error as it renders multiple times, is there any fix to it? or am i dooing something wrong.
Again thank you
Wow dude, I came from using react-transition-group, but since react router v6 arrived, this beautiful method is my salvation. Nice work of @anxiny and yours!
Thanks! Glad I can help π. Nice article!