3

I'm trying to provide dynamic custom props to a React Router Route definition using the newest React Router 6.4. I can't find any examples to showcase how I can accomplish this. These would be props that are provided from the parent component of the RouterProvider declaration.

An example from official documentation for 6.0 - 6.3:

// Ah, nice and simple API. And it's just like the <Suspense> API!
// Nothing more to learn here.
<Route path=":userId" element={<Profile />} />

// But wait, how do I pass custom props to the <Profile>
// element? Oh ya, it's just an element. Easy.
<Route path=":userId" element={<Profile animate={true} />} />

In 6.4, your route definition looks like something like:

// How do I provide animate state from App component to Policy component?
const router = createBrowserRouter([{ path: '/', element: <Profile animate={animate} /> }];

export function App() {
    const [animate, setAnimate] = useState(true);
    return <RouterProvider router={router} />
}
hanneswidrig
  • 185
  • 2
  • 13
  • 1
    I don't understand your question. In the example you provided you are passing an `animate` prop to the routed component. RRDv6.4.0 didn't change the `Route` component API. Is your question really about passing a dynamic prop value when the route is accessed? Can you edit to provide a more representative [mcve] for what you are trying to do? – Drew Reese Sep 22 '22 at 15:50
  • @DrewReese I should have made it more clear, that is precisely what I am asking. Previously, react-router Route components lived within a React component tree where dynamic props could be provided. Now the route configuration lives outside the React component tree. I can produce a minimal reproducible example later. – hanneswidrig Sep 22 '22 at 15:57
  • 1
    I figured as much. There's is nothing that state the `router` has to be declared outside any component. `createBrowserRouter` is a utility function more or less, and it's use case isn't all that different from the older/existing `useRoutes` hook that also takes a routs configuration and returns the current routes object to be rendered. – Drew Reese Sep 22 '22 at 16:03

2 Answers2

7

In the example you provided you are already passing an animate prop to the routed component. RRDv6.4.0 didn't change the Route component API. It seems your question is really rather about passing a dynamic prop value when the route is accessed.

Move the router declaration into the App component so the animate state is in scope.

Example:

function App() {
  const [animate, setAnimate] = useState(true);

  const router = createBrowserRouter([
    { path: "/", element: <Profile animate={animate} /> } // <-- pass prop value
  ]);

  return (
    <div className="App">
      ...
      <RouterProvider router={router} />
    </div>
  );
}

Edit how-to-provide-custom-props-to-react-router-6-4-route

To avoid recreating the router each time the parent component rerenders you could memoize it with the useMemo hook.

function App() {
  const [animate, setAnimate] = useState(true);

  const router = useMemo(() => {
    return createBrowserRouter([
      { path: "/", element: <Profile animate={animate} /> }
    ]);
  }, [animate]);

  return (
    <div className="App">
      ...
      <RouterProvider router={router} />
    </div>
  );
}

Another alternative would be to move the state into a layout route and pass down the state via the layout route's Outlet context provider. Consumers use the useOutletContext hook to access the provided value. This means the router declaration can be moved out of the React component and be provided as a stable reference.

Example:

const Profile = () => {
  const { animate } = useOutletContext();

  ...

  return (
    ...
  );
};
const AppLayout = () => {
  const [animate, setAnimate] = useState(true);

  return (
    <>
      ...
      <Outlet context={{ animate, /* other values */ }} />
      ...
    </>
  );
};

const router = createBrowserRouter([
  {
    element: <AppLayout />,
    children: [{ path: "/", element: <Profile /> }]
  }
]);

export default function App() {
  return (
    <div className="App">
      <RouterProvider router={router} />
    </div>
  );
}

Edit how-to-provide-custom-props-to-react-router-6-4-route (forked)

Drew Reese
  • 165,259
  • 14
  • 153
  • 181
  • 2
    Thanks for showing this, should I have any concerns about router being recreated whenever the App component is re-rendered? – hanneswidrig Sep 22 '22 at 16:02
  • 1
    @hanneswidrig I am inclined to say no. If you were using the older `BrowserRouter` and declaring all the routes as JSX and the state in `App` updates, the entire ReactTree from `App` down will be rerendered. Like I said in my comment above in your question, this isn't all too different to how the `useRoutes` hook works. – Drew Reese Sep 22 '22 at 16:04
  • 1
    @hanneswidrig To confirm this behavior (*for myself to ensure I'm not mistaken*) I've adding mounting and rendering `useEffect` hooks to the `Profile` component. `Profile` mounts once when the route path matches, and only rerenders from there when the `animate` prop value changes. Note that you'll see two mounting logs because the `React.StrictMode` component and the sandbox code is a non-production build. This is expected behavior. – Drew Reese Sep 22 '22 at 16:08
  • @DrewReese how would a nav bar work in this case? If you needed a nav bar to render on every page you would need to add it before the `RouterProvider` which means you would then lose access to using `Link` inside the nav. – Marcellino Ornelas Jun 08 '23 at 01:43
  • 1
    @MarcellinoOrnelas You'd render a navbar, or any other "common UI", via a [layout route](https://reactrouter.com/en/main/start/concepts#layout-routes). Layout routes render the common UI and an [Outlet](https://reactrouter.com/en/main/components/outlet) for nested routes to render their content into. See this [answer](/a/69999387/8690857) of mine for an example. – Drew Reese Jun 08 '23 at 02:11
  • @DrewReese Thank you! Thats exactly what i needed. – Marcellino Ornelas Jun 13 '23 at 20:01
  • @DrewReese I'm not sure you're right about the "not rerendering" thing. If you [add another state to your codesandbox](https://codesandbox.io/s/how-to-provide-custom-props-to-react-router-6-4-route-forked-g8f65p), you can see the profile component rerendering even when unrelated state is changed. Completely possible that I'm just demonstrating this incorrectly though. [The docs](https://reactrouter.com/en/main/routers/create-browser-router) also suggest suggest that "you should create your router outside of the React tree with a statically defined set of routes". – MHebes Jul 26 '23 at 20:02
  • @MHebes Can you clarify what "not rendering" thing you are referring to? True, if you add *more* state in an ancestor component and update it, then *that* component will rerender itself and its entire sub-ReactTree, which includes the router, route, and the `Profile` component. I totally agree, typically you *would* declare the routes outside a React component. That doesn't work well though when you have some parent props that you want/need to pass down to a route's element component, e.g. the `animate` state. – Drew Reese Jul 26 '23 at 20:16
  • 1
    @MHebes In your sandbox demo if you decorate the `Profile` component in the `React.memo` HOC you'll notice it only rerenders when `animate` prop updates. In other words, if you are applying this solution to code on your end and are seeing extraneous renders that there are tools available to mitigate unnecessary rerenders. – Drew Reese Jul 26 '23 at 20:23
  • @DrewReese Sorry for not being clear. By "not rendering" thing, I was referring to this (paraphrased) interaction: Q: "Should I have concerns about router being recreated" A: "I'm inclined to say no." I understand what you were saying now, thank you for clarifying. – MHebes Jul 26 '23 at 21:47
  • As a separate comment chain to above: it does appear that the maintainers [intend your router to be used as a singleton](https://github.com/remix-run/react-router/discussions/10440#discussioncomment-5968821). Not sure what to make of that. – MHebes Jul 26 '23 at 21:49
  • 1
    @MHebes "Should" and "Intention" are really just suggested guidelines, right? My example is *just* the most basic implementation. Obviously if recreating the router becomes an actual issue you can memoize it with the `useMemo` hook. The gist is that the Data routers don't cover 100% of the use cases. I agree that abstracting the state in to a provider is probably the next best solution. I'll see about creating a demo for that and adding it to my answer as a suggested improvement over the original code. Thanks for the feedback, BTW, I appreciate it. – Drew Reese Jul 26 '23 at 22:04
1

Routes I am not sure but I think that you have to use Routes in App component https://reactrouter.com/en/main/components/routes

  • While this link may answer the question, it is better to include the essential parts of the answer here and provide the link for reference. Link-only answers can become invalid if the linked page changes. - [From Review](/review/late-answers/33342676) – Lord-JulianXLII Dec 08 '22 at 14:46