简体   繁体   中英

Organizing React routes into separate components

I'm trying to find a way to organize my routes to assist the dev who might be taking over my work in the future. I thought of separating my <Route /> entries into separate components and then just load those into a main component similar to how users are assigned groups.

The issue is that when using more than one component only the first one works. This might not be the most react way of doing this so I'm also open to alternatives.

Original route arrangement

const AllRoutes = () => {
    return (
        <Switch>
            {/* public routes*/}
            <Route path={'/about'} component={AboutView} />
            <Route path={'/project'} component={ProjectView} />
            <Route path={'/contact'} component={ContactView} />
            
            {/* auth routes */}
            <Route path={'/login'} component={LoginView} />
            <Route path={'/logout'} component={LogoutView} />

            <Route component={Error404View} />
        </Switch>
    )
}

Separating the public routes from the auth ones:

const PublicRouteGroup = () => {
    return (
        <>
            <Route path={'/about'} component={AboutView} />
            <Route path={'/project'} component={ProjectView} />
            <Route path={'/contact'} component={ContactView} />
        </>
    )
}

const AuthRouteGroup = () => {
    return (
        <>
            <Route path={'/login'} component={LoginView} />
            <Route path={'/logout'} component={LogoutView} />
        </>
    )
}

This way I can use it as such:

const AllRoutes = () => {
    return (
        <Switch>
            <PublicRouteGroup />    {/* This works */}
            <AuthRouteGroup />      {/* This doesn't */}


            {/* This 404 is not a route group */}
            <Route component={Error404View} />
        </Switch>
    )
}

Flipping <PublicRouteGroup /> and <AuthRouteGroup /> only changes the order:

const AllRoutes = () => {
    return (
        <Switch>
            <AuthRouteGroup />      {/* This works */}
            <PublicRouteGroup />    {/* This doesn't */}

            {/* This 404 is not a route group */}
            <Route component={Error404View} />
        </Switch>
    )
}

Update #1

This is thanks to @skyboyer. By moving the <Switch> to the child components and removing it from the AllRoutes component each component started to show. It appears adding the <Switch> in AllRoutes is allowing only the first hit to show which is as <Switch> does. But now by removing it it shows the 404 at the end of each page as well.

Basically, it looks like this:

const AllRoutes = () => {
    return (
        <>
            <Route component={AuthRouteGroup} />      {/* This works */}
            <Route component={PublicRouteGroup} />    {/* This also works */}

            {/* This 404 is not a route group */}
            <Route component={Error404View} />        {/* Always shown at the bottom */}
            {/* Even putting the 404 in its own RouteGroup yields the same issue */}
        </>
    )
}

It appears this current set up of treating components like OOP classes you can extend from is the wrong approach. I've instead made use of arrays since these can be acted upon by the spread operator. It still accomplishes the same goal of organizing routes across an infinite number of groups which was what I was after.

Create the array for each group

const public_route_group = [
    {path: '/about', component: AboutView},
    {path: '/project', component: ProjectView},
    {path: '/contact', component: ContactView},
]

const auth_route_group = [
    {path: '/login', component: LoginView},
    {path: '/logout', component: LogoutView},
]

const error_route_group = [
    {component: Error404View}   // No path required
]

const user_routes = [
    ...public_route_group,
    ...auth_route_group,
    ...error_route_group
]

Create the routes

const AllRoutes = () => {
    return (
        <Switch>
            {user_routes.map((route, idx) => {
                return <Route key={idx} {...route} />
            })}
        </Switch>
    )
}

I figure this can also be modified further if you're using nested objects in your array.

I'd like to thank @skyboyer for providing an insight into this problem.

How about having it without Swtich at top-level

  <Route component={PublicRouteGroup} />
  <Route component={AuthRouteGroup} />      

so they are rendered unconditionally. And then having extra Switch in your components like


const AuthRouteGroup = () => {
    return (
        <Switch>
            <Route path={'/login'} component={LoginView} />
            <Route path={'/logout'} component={LogoutView} />
        <Switch/>
    )
}

But why id did not work?

The reason is how Switch works :

React.Children.forEach(this.props.children, child => {
  if (match == null && React.isValidElement(child)) {
    element = child;
    const path = child.props.path || child.props.from;

    match = path
      ? matchPath(location.pathname, { ...child.props, path })
      : context.match;
  }
});

See, even if AuthRouteGroup is not a Route , Switch anyway looks to its props.path . And once undefined for props.path matches any path and Switch renders only first matching Route you are getting only first component rendered.

[UPD] "does-not-match-any-route" View will work only at top level of Switch . Also there are no way to know if some nested children of sibling element has matched current route or not. So only way I see is listing all routes in single place.

Alternative that looks rather poor is having special route "/error404" and redirect user to it from inside of other components(but who should decide? and where? and when?).

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM