2

While trying to make nested routes in react router dom v5 I found this answer that explains how to do it very well

(please take a look at the code here because it's a little bit difference from the answer mentioned above)

Layouts.js

const NotFound = () => <h1>Not Found</h1>;

function Layouts() {
  return (
    <Switch>
      <Route path="/auth" component={AuthLayout} />
      <Route path="/app" component={AppLayout} />
      <Route path="/" component={NotFound} />
    </Switch>
  );
}

AuthLayout

const Signup = () => <p>Login</p>;
const Login = () => <p>Sign up</p>;

function AuthLayout() {
  return (
    <div>
      <h1>Auth Layout</h1>
      <Route path="/auth/signup" exact component={Signup} />
      <Route path="/auth/login" exact component={Login} />
      {/* Commenting this because I want to go to NotFound component */}
      {/* <Redirect from="/auth" to="/auth/login" exact /> */}
    </div>
  );
}

AppLayout

const Home = () => <p>Home</p>;
const Dashboard = () => <p>Dashboard</p>;

function AppLayout() {
  return (
    <div>
      <h1>App Layout</h1>
      <Route path="/app/home" exact component={Home} />
      <Route path="/app/dashboard" exact component={Dashboard} />
      {/* Commenting this because I want to go to NotFound component */}
      {/* Redirect from="/app" to="/app/home" exact /> */}
    </div>
  );
}

But this have one problem, that if you go to a route with /app/somethingnotfound it won't go to <Route path="/" component={NotFound} />, it will "stay inside" AppLayout and render no route.

How can I make /app/somethingnotfound go to <Route path="/" component={NotFound} /> in this case?

Edit:

Just to be more clear: I don't want to just add <Route component={NotFound} /> inside AuthLayout and AppLayout because it will render other things. What I really need is to show the top level NotFound.

Drew Reese
  • 165,259
  • 14
  • 153
  • 181
Vencovsky
  • 28,550
  • 17
  • 109
  • 176

3 Answers3

3

Not found component usually works like this:

<Router>
  <Switch>
    <Route exact path="/auth" component={AuthLayout} />
    <Route exact path="/app" component={AppLayout} />
    <Route component={NotFound} />
  </Switch>
</Router>

But you cannot mark /auth and /app as exact as they contain nested routes. So, you should do:

<Router>
  <Switch>
    <Route path="/auth" component={AuthLayout} />
    <Route path="/app" component={AppLayout} />
    <Route exact path="/404" component={NotFound} />
    <Redirect to='/404' />
  </Switch>
</Router>

and your component (e.g. AppLayout) with nested routes:

<>
<h1>App Layout</h1>
<Switch>
  <Route path="/app/home" exact component={Home} />
  <Route path="/app/dashboard" exact component={Dashboard} />
  <Redirect to="/404" />
</Switch>
</>
Ajeet Shah
  • 18,551
  • 8
  • 57
  • 87
  • I said in the question, but I think it wasn't very clear, what I mean by "How can I make /app/somethingnotfound go to in this case?" means that I I want to show the `NotFound` component from the `Layouts` component, because the way you are doing, it's showing everything that is inside `AppLayout` (in you asnwer you removed it, but it would show `

    App Layout

    `) which isn't what I need
    – Vencovsky Jun 20 '20 at 17:36
0

While looking in github issues I found out this solution, you create a "global not found page" where you pass through the state if the route isn't found, and just render that instead of the other routes.

const NotFound = () => <div className="not_found"><h1>Not Found</h1></div>;

const RouteNotFound = () => <Redirect to={{ state: { notFoundError: true } }} />;

const CaptureRouteNotFound = withRouter(({children, location}) => {
  return location && location.state && location.state.notFoundError ? <NotFound /> : children;
});

const Settings = () => {
  return (
    <Switch>
      <Route path="/settings/account" render={() => <h1>Account Settings</h1>} />
      <Route path="/settings/profile" render={() => <h1>Profile Settings</h1>} />

      <RouteNotFound />
    </Switch>
  );
};

const AppShell = ({children}) => {
  return (
    <div className="application">
      <header>Application</header>
      {children}
    </div>
  );
};

const Application = () => {
  return (
    <Router>
      <CaptureRouteNotFound>
        <AppShell>
          <Switch>
            <Route path="/settings" render={() => <Settings />} />
            <Route path="/profile" render={() => <h1>User Profile</h1>} />

            <RouteNotFound />
          </Switch>
        </AppShell>
      </CaptureRouteNotFound>
    </Router>
  );
};
Vencovsky
  • 28,550
  • 17
  • 109
  • 176
0

<Route path="/" component={NotFound} /> doesn't match for all the nested that are not implemented, because the Switch in the Layouts component will only render the first Route child that matches the pathname. In your case, it matches the route with AppLayout component when you go the path /app/somethingdoesntexist that is why it only renders the AppLayout component.

Solution

It is better to implement a NotFound route for each layout using nested Switch components, For example

function AppLayout() {
  const { path, url } = useRouteMatch();

  return (
      <Switch>
        <Route path={`${path}/home`} exact component={Home} />
        <Route path={`${path}/dashboard`} exact component={Dashboard} />
        <Route path="*" component={NotFound} />
      </Switch>
  );
}

In the case above we got the matched path by using useRouteMatch hook and nest another Switch component that will render any nested route that matches from the path and we provide a fallback Route specifying the path as "*" which will render the NotFound component when there is no match found.

Here is a complete example of the solution codesandbox.

You can also see the nesting example provided in the docs.

subashMahapatra
  • 6,339
  • 1
  • 20
  • 26
  • I said in the question, but I think it wasn't very clear, what I mean by "How can I make /app/somethingnotfound go to in this case?" means that I I want to show the `NotFound` component from the `Layouts` component, because the way you are doing, it's showing everything that is inside `AppLayout` (in you asnwer you removed it, but it would show `

    App Layout

    `) which isn't what I need
    – Vencovsky Jun 20 '20 at 17:36
  • Yes, it will show the `

    App Layout

    ` if you render it outside the nested switch in `AppLayout`. But do you absolutely need to render the h1 outside the switch? You can make use of another route that renders the h1 inside for the base path and get the same exact result or is it not what you need. It is still a bit unclear to me what exact result you are trying to achieve. Please have a look at the codesandbox in the answer and if it doesn't satisfy the result let me know where it fails.
    – subashMahapatra Jun 20 '20 at 17:48
  • I have updated the [codesandbox](https://codesandbox.io/s/jolly-wildflower-nlyhu?file=/src/App.js) in the answer. Please have a look and let me know if doesn't satisfy your need. – subashMahapatra Jun 20 '20 at 18:09