6



I recently jumped the gun on upgrading to the latest react-router-dom and their fancy new Data APIs, and this essentially meant rewriting my app's routing logic using the new object-based routing instead of the V5 JSX syntax with Routes. I was wondering if anyone here has managed to get route transitions working with react-router V6? 
 


In V5 it was possible to have nested route animations declared in the following manner:



<AnimatePresence>
    <Routes location={location} key={locationArr[1]}>
         <Route path="/" />

         <Route path="/nested/*">
            <AnimatePresence>
                <Routes location={location} key={locationArr[2]}>
                    <Route path="/nested/1" />
                    <Route path="/nested/2" />
                </Routes>
            </AnimatePresence>
         </Route>
     </Routes>
</AnimatePresence>

Managing the nested Routes object key this way allowed to perform page transitions only on the changed nested route: the parent route would not re-render when navigating to different child routes. 

But now with V6, Routes-object is no more, and I’m having some troubles implementing the same functionality as in V5.

I’ve created a codesandbox to try and replicate V5 nested child route animation functionality. The sandbox uses a stable useOutlet reference for both parent and nested routes, following discussion at https://github.com/remix-run/react-router/discussions/8008#discussioncomment-1280897.



Codesandbox: https://codesandbox.io/s/cool-cherry-wjrvhl?file=/src/App.tsx



Using latest react, react-router-dom, typescript and framer-motion.

In the sandbox it’s visible that the parent route animations work as expected, but the nested routes will not render at all, presumably because the parent Outlet reference is frozen. Only way to make the content update is to refresh the page. Changing RootLayout’s key from locationArr[1] to location.pathname will re-render the nested routes correctly, but this means that the whole page layout rerenders, which is to be expected but not desirable: my app has a Header component that is visible on all child routes, and I'd like that only the Outlet would animate when navigating between nested child routes while the Header stays visible and stationary during the child route animation.

Desired result would be identical to this sandbox that does not use the Data APIs: https://codesandbox.io/s/animated-nested-routes-demo-react-router-v6-forked-lxf1bf

I'm starting to think that this might not be possible with the V6 data API structure, but curious to see if anyone else has been struggling / coming up with solutions for this. All help appreciated!

2
  • I'm a little lost in your code examples since they are using RRDv6 code syntax and APIs, not RRDv5. Are you saying the code worked with pre-RRDv6.4 code? All of RRDv6 has route objects, that's how the Route components work. Commented Nov 7, 2022 at 19:16
  • Apologies, I should've been more clear in my original question: I upgraded from v5 to v6.4.3 and converted my old (working) RRDv5 code to the new object-based syntax using createBrowserRouter. The code sandbox has my attempt at making route animations work with the new syntax. Commented Nov 7, 2022 at 20:15

1 Answer 1

2

I don't see where any of the code you are using uses any of the new [email protected] Data APIs, but if you are simply wanting to convert the pre-RRDv6.4 code to use the new createBrowserRouter utility to create the BrowserRouter then you can accomplish this with also using the createRoutesFromElements utility function.

Example:

import { createRoot } from "react-dom/client";
import "./main.css";
import {
  createBrowserRouter,
  createRoutesFromElements,
  RouterProvider,
  Routes,
  Route,
  Link,
  useLocation
} from "react-router-dom";
import { motion, AnimatePresence } from "framer-motion";
import { MOTION_VARIANTS } from "./constants";

...

function App() {
  const location = useLocation();

  const locationArr = location.pathname?.split("/") ?? [];

  return (
    <div className="Main">
      <AnimatePresence>
        <Routes location={location} key={locationArr[1]}>
          <Route
            path="/"
            element={
              <Page title="">
                <Link to="../page1">Go to page 1</Link>
              </Page>
            }
          />
          <Route
            path="/page1/*"
            element={
              <Page title="Page 1">
                <AnimatePresence>
                  <Routes location={location} key={locationArr[2]}>
                    <Route
                      path="/*"
                      element={
                        <NestedPage
                          title="Nested Page 1"
                          nextPath="../nested2"
                        />
                      }
                    />
                    <Route
                      path="/nested2"
                      element={
                        <NestedPage
                          title="Nested Page 2"
                          nextPath="../nested3"
                        />
                      }
                    />
                    <Route
                      path="/nested3"
                      element={
                        <NestedPage title="Nested Page 3" nextPath="../" />
                      }
                    />
                  </Routes>
                </AnimatePresence>
                <Link to="../page2">Next page</Link>
              </Page>
            }
          />
          <Route
            path="/page2/*"
            element={
              <Page title="Page 2">
                <Link to="../page1">Next page</Link>
              </Page>
            }
          />
        </Routes>
      </AnimatePresence>
    </div>
  );
}

...

// A simple page with a title (this will be top-level routing)
const Page = ({
  title,
  children
}: {
  title: string;
  children?: JSX.Element | JSX.Element[];
}) => {
  return (
    <motion.div
      className="Page"
      custom={{ direction: "forward" }}
      initial="initial"
      animate="in"
      exit="out"
      variants={MOTION_VARIANTS}
      style={{ width: "100%", position: "absolute", top: 0, left: 0 }}
    >
      <h1>{title}</h1>
      <>{children}</>
    </motion.div>
  );
};

// A page which will be nested inside another page. So this will be within a nested route.
const NestedPage = ({
  title,
  nextPath
}: {
  title: string;
  nextPath: string;
}) => {
  return (
    <motion.div
      className="NestedPage"
      custom={{ direction: "forward" }}
      initial="initial"
      animate="in"
      exit="out"
      variants={MOTION_VARIANTS}
      style={{ width: "100%", position: "absolute", top: 200, left: 0 }}
    >
      <h2>{title}</h2>
      <p>
        This is an element in a group of nested pages
        <br />
        <Link to={nextPath}>Next nested page</Link>
      </p>
    </motion.div>
  );
};

Here's where the "magic" is. Render App into a "catch all" route that is passed to the createRoutesFromElements utility passed to the createBrowserRouter function to create the new Data router. Pass the router to the RouterProvider component.

const container = document.getElementById("root");
const root = createRoot(container!);

const router = createBrowserRouter(
  createRoutesFromElements(<Route path="*" element={<App />} />)
);

root.render(<RouterProvider router={router} />);

Edit nested-child-route-transitions-in-react-router-dom-6-4-3-using-framer-motion

Sign up to request clarification or add additional context in comments.

4 Comments

Yeah, the included codesandbox is merely a minimal reproduction of the issue and hence doesn't have anything extra, just the route transitions and new RRD syntax. With createRoutesFromElements it's certainly possible to make my current implementation work, but I assumed using the helper was more of an intermediary solution that would eventually lose support in newer RRD versions. But perhaps I'm mistaken on this part. Thanks, I'll probably go with this unless there's a way to make transitions work with the new syntax in a similar fashion.
@Rutkula I'm pretty sure that anything you are writing/describing in JSX as "elements" can also be described via a configuration. This is what RRDv6 does under-the-hood anyway with the Routes and Route components, I think they are just exposing out a bit more of that functionality via the createRoutesFromElements utility. I'll see about creating a version that uses a configuration directly.
This looks great but I changed my router to this and no loader functions get called anymore, I thought they would are you are still using the data APIs router
@JoeMethven In order for the Route components with loader to work they all need to be declared in the createBrowserRouter when creating the Data router. The loader prop has no effect if passed on a descendent route since the Data router hasn't any idea what the future dynamic content could be.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.