molefrog / wouter

🥢 A minimalist-friendly ~2.1KB routing for React and Preact

Home Page:https://npm.im/wouter

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Nested routing broken by intermediate component

ukaaa opened this issue · comments

I am trying to achieve nested routing but with a custom component in between.

The snippet below does not give the expected result. When I click on the first link/a-base-page it renders the expected <Route path="/">SubPageA</Route>. But when I click on the second link /a-base-page/sub-page-1 it falls back to the <Route path="*">. I was expecting it to render <Route path="/sub-page-1">SubPageA-1</Route>.

It seems that the additional level introduced by ProtectedRoute breaks the nested routing.

If I replace <ProtectedRoute path="/a-base-page" childRoutes={ChildRoutes} /> with <Route path="/a-base-page" component={ChildRoutes} nest /> it works again.

How can I make this work? I am the owner of ChildRoutes. But ProtectedRoute and App are owned by another team. The goal is to decouple our routing.

Code sandbox: https://codesandbox.io/p/sandbox/wouter-3-0-0-issue-410-94fp9f

function ChildRoutes() {
  return (
    <Switch>
      <Route path="/sub-page-1">SubPageA-1</Route>
      <Route path="/sub-page-2">SubPageA-2</Route>
      <Route path="/">SubPageA</Route>

      <Route>Default route for switch nested in A</Route>
    </Switch>
  );
}

function ProtectedRoute({ path = "", childRoutes: ChildRoutes }) {
  const canAccess = true; // some logic dependant on the signed-in user
  return canAccess ? (
    <Route path={path} nest>
      <ChildRoutes />
    </Route>
  ) : (
    <Redirect to="/" />
  );
}

function App() {
  return (
    <div className="App">
      <nav>
        <ActiveLink href="/a-base-page">A (/)</ActiveLink>
        <ActiveLink href="/a-base-page/sub-page-1">A (1)</ActiveLink>
        <ActiveLink href="/a-base-page/random-page">A (404)</ActiveLink>
      </nav>

      <main>
        <Switch>
          <Route path="/">HomePage</Route>

          <ProtectedRoute path="/a-base-page" childRoutes={ChildRoutes} />

          <Route path="*">
            {(params) => <>404! Params is {JSON.stringify(params)}</>}
          </Route>
        </Switch>
      </main>
    </div>
  );
}

Switch can only work with direct children, because it iterates over JSX so matching is done before child components are even rendered. This is by design and the reason was to keep the size small, otherwise we had to wrap everything in extra context. I'm not sure how to explain this better, but maybe the source code can help.

How you can fix this: move canAccess inside your App component and rewrite the switch like so:

 <Switch>
          <Route path="/">HomePage</Route>

          {canAccess ? <Route path="/a-base-page" nest><ChildRoutes /></Route> : <Redirect to="/" />}

          <Route path="*">
            {(params) => <>404! Params is {JSON.stringify(params)}</>}
          </Route>
        </Switch>

Thank you for getting back to me so quickly. Your reply inspired me to rewrite a piece of the code. It works now 👍