Vencovsky
Vencovsky

Reputation: 31565

Nested routes and not found route in react router dom v5

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.

Upvotes: 2

Views: 1877

Answers (3)

Ajeet Shah
Ajeet Shah

Reputation: 19813

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>
</>

Upvotes: 3

subashMahapatra
subashMahapatra

Reputation: 6837

<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.

Upvotes: 0

Vencovsky
Vencovsky

Reputation: 31565

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>
  );
};

Upvotes: 0

Related Questions