React-router: V6 looked great before the "element" prop :/

Created on 12 Feb 2020  路  14Comments  路  Source: ReactTraining/react-router

This is a great routing API in my opinion. An API that I was excited about. It's just React. Passing props to Home is intuitive and obvious. And clean and readable.

<Route path="/">
    <Home/>
</Route>

This is less flexible, intuitive, and readable in my opinion.

<Route path="/" element={<Home />} />

I read the reasoning in the migrating guide, but I do not think the two reasons given make sense:

1) Suspense is not a fair comparison. Suspense already uses children to render, so Suspense's "fallback" prop kind of makes sense for it, but not for Route.

2) Reserving children for nesting routes does make sense for route config usage, but not if you prefer V4 style routing without a route config. A route config and V4 style routing are different use cases.

I prefer the V4 style of routing, so I hope there is a way to use children for that style as well. Is there a way to accommodate both styles of routing using children?

Most helpful comment

I'm confused why we have the Route component at all now. It's sole job is to return the element prop (and do propTypes in dev).

Why not just get rid of it and out the component in the tree with the appropriate props for Routes to read?

All 14 comments

I'm confused why we have the Route component at all now. It's sole job is to return the element prop (and do propTypes in dev).

Why not just get rid of it and out the component in the tree with the appropriate props for Routes to read?

I think it would feel weird for <Routes> to traverse the element tree and read React Router props in user-defined components, if that's what you're saying @timdorr.

I do agree that <Route> has become so thin that some might not even want to use it. Reading the migration guide it became clear that useRoutes is interchangeable with <Routes> (in fact, the latter is built with the former) and one could build a React Router v6 app without using <Route> at all, just using useRoutes.

Whether <Route> should stay in the API is a question that I don't know the answer to. My guess is that a large chunk of React Router users would want to keep declarative routes using React elements. <Route> gives them that. For those that don't, useRoutes is there.

@cquillen2003 We have solid reasons for using the element prop, most of which I already covered in the migration guide.

@timdorr We have the <Route> element because it's easier to type for people using TypeScript. The "throw whatever element you want in your route config" approach Ryan used in reach/router didn't work out so well for people using TypeScript.

Whether should stay in the API is a question that I don't know the answer to.

Yep, you nailed it @migueloller. Some people might want to use the JSX API, some might prefer useRoutes. I like the JSX API for small sites, but I would probably ditch it if I had thousands of routes.

@mjackson Would you mind elaborating some? I did read the migration guide and the reasoning behind the element prop.

I made the following points in my original post and quite a few people agreed. Please re-consider and/or address them if possible.

1) Suspense is not a fair comparison. Suspense already uses children to render, so Suspense's "fallback" prop kind of makes sense for it, but not for Route.

2) Reserving children for nesting routes does make sense for route config usage, but not if you prefer V4 style routing without a route config. A route config and V4 style routing are different use cases.

@mjackson it also appears this can break typescript. Say, if you have
declare const Home: ComponentType<{ children: ReactNode }> (required one child),
then <Route path="/" element={<Home />}>... will not pass type check

but I would probably ditch it if I had thousands of routes.

I guess if it's to stay, some still prefer it to be more intuitive

@cquillen2003

  1. A <Route> uses both element and children props to render. Using children for route nesting is cleaner than using another named prop because JSX has built-in syntax for nesting elements.
  2. Anything you could do with v4 you can do with v6. If you just need a single route:
<Routes>
  <Route path="/" element={<Whatever />} />
</Routes>

@sonhanguyen You can still provide children to <Home> if they are required.

<Route path="home" element={<Home><ChildrenGoHere /></Home>} />

@mjackson Thanks for getting back to me. I know v4-style routing is still possible, it just feels like v6 is optimizing for the route-nesting use case.

I've done routing in Ember, Angular, and React Router (starting with v3). The v4-style took me a couple of hours to understand, but I have come to think it is the best routing API. I did miss the route config with SSR, but a pre-render to determine data-fetching needs is what I settled on. Now, I think v4-style routing with a route config/map generated with a build step might be the best of both worlds. Have you and Ryan considered that?

@cquillen2003 Yes, we are optimizing for more than one route, because that's by far the most common case. If you need only a single route, you can always use useMatch (which acts like a single route, some minor improvements coming soon here) or feel free to create your own wrapper:

function V4RouteWithoutNesting({ children, ...props }) {
  return <Routes><Route {...props} element={children} /></Routes>;
}

I also would like to use the Route component outside of the Routes component. In v5 it is really handy if you want to show something only on a sub-route.

@mjackson I think we are talking about different things. My point is that v6 seems to be optimized for "static, route-config" style routing rather than the "dynamic" style introduced by v4.

@cquillen2003 Yes, that's certainly true. But it's based on real world usage. Most people use React Router with a single Switch near the top level of their tree. There is certainly a use case for more dynamic routing, but it hasn't proven to be likely that there's more than a couple cases of that in real world apps. By putting more emphasis on a great experience for centralized configs, we're better aligning with the way most folks use the library.

@mjackson

You can still provide children to <Home> if they are required.

<Route path="home" element={<Home><ChildrenGoHere /></Home>} />

Yes but I'm not supposed to do that, it's react-router that puts the children should it match, doesn't it?

<Route path="home" element={<Home />}>
    <Route path="child" element={<ChildrenGoHere />} />
    //...

renders <Home><ChildrenGoHere /></Home>?

It depends on if you need a different route for the children, @sonhanguyen. If all you need is some children, feel free to use the children prop as you would with any other React element. If you only want those children to render at a given URL path (a "route"), then use a separate <Route> and an <Outlet> in the parent component.

I also would like to use the Route component outside of the Routes component. In v5 it is really handy if you want to show something only on a sub-route.

@MeiKatz I get that. With the current v6 alpha, you can just useMatch and be on your way. If you'd prefer a component API (instead of a hook), you can always:

import { useMatch } from 'react-router-dom';

function Match({ path, caseSensitive, children }) {
  let match = useMatch({ path, caseSensitive });
  return match == null ? null : children;
}

function App() {
  return (
    <div>
      <Match path="/">
        {/* SidebarNav renders only when the path is / */}
        <SidebarNav />
      </Match>

      <p>The rest of your app...</p>
    </div>
  );
}
Was this page helpful?
0 / 5 - 0 ratings

Related issues

ackvf picture ackvf  路  3Comments

ArthurRougier picture ArthurRougier  路  3Comments

davetgreen picture davetgreen  路  3Comments

tomatau picture tomatau  路  3Comments

wzup picture wzup  路  3Comments