I recently added page transition to my React app using the library Framer Motion and React-Router-DOM. However, all layout components such as sidebar, navbar, etc. have been experiencing unwanted rerenders upon page change. Below is the snippet for my router and layout:
Router:
<AnimatePresence mode="wait">
<Routes location={location} key={location.pathname}>
<Route element={<GuestMiddleware />}>
// Routes without layout
</Route>
<Route element={<AuthMiddleware />}>
<Route element={<AdminLayout />}> // <-- Here is the Layout
// Routes with layout
</Route>
</Route>
</Routes>
</AnimatePresence>
Layout:
<>
<Sidebar />
<div className={cn("transition-spacing flex min-h-d-screen w-full flex-col ps-0 duration-300", { "lg:ps-sidebar": isOpen })}>
<Topbar />
<div className="grow">
<Outlet />
</div>
</div>
</>
I actually found the reason for rerenders being the key attribute in the Routes
component. But then once I remove it, all page exit transitions tend to behave incorrectly. I want to know if there is a way to fix or even a possible workaround regarding this.
It seems the issue is that the Sidebar
component is rendered by the Layout
component within the Routes
using the current location.pathname
as a React key. When the key changes, e.g. when the route location changes, the Routes
component is remounted, including its entire sub-ReactTree.
The solution is to move Sidebar
out of the AnimatedPresence
so it's not remounted when animating the route transitions.
Layout.tsx
Remove Sidebar
from the Layout
component.
import { Outlet } from "react-router-dom";
import Topbar from "../components/Topbar";
import { cn } from "../utils/cn";
import { useSidebarStore } from "../features/stores/sidebar";
const Layout = () => {
const { isOpen } = useSidebarStore();
return (
<main
className={cn(
"transition-[padding] duration-300 relative flex flex-col w-full min-h-screen",
isOpen ? "ps-[300px]" : "ps-0",
)}
>
<Topbar />
<div className="grow">
<Outlet />
</div>
</main>
);
};
export default Layout;
router.tsx
Render Sidebar
in the Router
outside AnimatePresence
.
import { useLocation, Route, Routes } from "react-router-dom";
import { AnimatePresence } from "framer-motion";
import Layout from "./layouts/Layout";
import Home from "./pages/Home";
import About from "./pages/About";
import Contact from "./pages/Contact";
import Preferences from "./pages/settings/Preferences";
import Users from "./pages/settings/Users";
import Sidebar from "./components/Sidebar";
const Router = () => {
const location = useLocation();
return (
<>
<Sidebar />
<AnimatePresence mode="wait">
<Routes {...{ location, key: location.pathname }}>
<Route element={<Layout />}>
<Route index element={<Home />} />
<Route path="about" element={<About />} />
<Route path="contact" element={<Contact />} />
<Route path="settings">
<Route path="preferences" element={<Preferences />} />
<Route path="users" element={<Users />} />
</Route>
</Route>
</Routes>
</AnimatePresence>
</>
);
};
export default Router;