Search code examples
reactjsreact-routerframer-motion

Preserve Exit Animations with React Router v6 and Framer Motion


I'm currently working on a project that involves React Router and Framer Motion v6. I'm trying to preserve exit animations between routes, but I've run into a bit of a roadblock.

Does anyone have experience with this? Specifically, I'm trying to figure out how to achieve this using the new v6 syntax. I've tried using createRoutesFromElements and createBrowserRouter, but I'm unable to pass AnimatePresence as a parent to the routes or route components. While using the previous, old BrowserRouter works, I can't utilize the data loader with that.

Any pointers would be greatly appreciated.

function App() {
  const router = createBrowserRouter(
    createRoutesFromElements(
      <>
        <Route
          path="/"
          element={
            <AnimatePresence mode="wait">
              <PrivateRoutes>
                <ScrollToTop>
                  <Layout />
                </ScrollToTop>
              </PrivateRoutes>
            </AnimatePresence>
          }
        >
          <Route index element={<Dashboard />} />
          <Route
            path="/transactions"
            element={<Transactions />}
            loader={({ params }) => {
              console.log("PARAMS:", params);
              return {
                hello: "world",
              };
            }}
          />
          <Route path="/disputes" element={<Disputes />} />
          <Route path="/cards">
            <Route index element={<Cards />} />
            <Route path=":id" element={<CardsIdLayout />}>
              <Route path="details" element={<CardDetails />} />
              <Route path="transactions" element={<CardTransactions />} />
            </Route>
          </Route>
          <Route path="/users">
            <Route index element={<Users />} />
            <Route path=":id" element={<UsersDetailsLayout />}>
              <Route index element={<UserDetails />} />
              <Route
                path="transactions"
                element={<UsersTransactions />}
              />
              <Route path="cards" element={<UsersCards />} />
            </Route>
          </Route>
        </Route>
        <Route path="*" element={<NotFound />} />
        <Route path="/auth" element={<PublicRoutes />}>
          <Route path="login" element={<Login />} />
          <Route path="reset-password" element={<ResetPassword />} />
        </Route>
      </>
    )
  );

  return <RouterProvider router={router} />;
}


Solution

  • I have a similar setup and I was able to get exit animations to work while using a dynamic loader. I believe the trick is to use an animated outlet component.

    const AnimatedOutlet = () => {
        const o = useOutlet();
        const [outlet] = useState(o);
    
        return <>{outlet}</>;
    };
    
    <AnimatedPresence mode="wait">
        <AnimatedOutlet key={location} />
    </AnimatedPresence>
    

    Then in your Transactions element, you may need to put the loader data in state so that the data isn't lost as soon as you navigate away breaking the exit animation.

    const [data] = useState(useLoaderData())
    

    Hope this helps.