Jadam
Jadam

Reputation: 1765

React Router 4 No Match with nested Routes

I would like to have a global No Match route to handle all 404s. Once signed in, all primary views fall inside a layout wrapped with a header and footer. Though the NotFound component should render outside this layout.

Here is a link to a "working" code sample

Here is a simplified example of the code:

const App = () => (
  <Switch>
    <Route path="/register" component={Register} />
    <Route path="/signin" component={SignIn} />
    <Route path="/" component={Home} />
    <Route component={NotFound} />
  </Switch>
);

const Home = () => (
  <div>
    <Header />
    <main>
      <Switch>
        <Route exact path="/" component={Main} />
        <Route path="/list" component={List} />
        <Route path="/tasks" component={Tasks} />
      </Switch>
    </main>
    <Footer />
  </div>
);

The above code sample works in rendering the layout as I wish, but renders an empty layout on non matching urls. If I set the App root path to exact than I can get the NotFound but lose the Home routes

Though variations of using/not-using exact on the roots, moving the roots to the bottom of the list of Routes I can get portions of the expected behavior to work, but not all of it working in concert.

If I visit "/" I expect to see:

Some Header Thing
Home
Some Footer Thing

If I visit "/list" I expect to see:

Some Header Thing
List
Some Footer Thing

If I visit "/register" I expect to see:

Register

If I visit "/foobar" I expect to see:

Not Found

Upvotes: 0

Views: 1379

Answers (1)

andrewatkinson
andrewatkinson

Reputation: 71

This below will get the routes working, with relatively minor tweaks to your code, and as I don't want to assume too much I'm hesitant to restructure things a lot. But as you can see, this results in using the 'Home' component to lead into all the sections with headers and footers, with 'Not Found' catching the rest. However, it does flatten things a lot which could get unwieldy with a lot of routes.

const App = () => (
  <BrowserRouter>
    <Switch>
      <Route path="/register" render={() => <div>Register</div>} />
      <Route path="/signin" render={() => <div>Sign in</div>} />
      <Route path="/list" component={Home} />
      <Route path="/tasks" component={Home} />
      <Route exact path="/" component={Home} />
      <Route render={() => <div>Not Found</div>} />
    </Switch>
  </BrowserRouter>
);

const Home = () => (
  <div>
    <header>Some Header Thing</header>
    <main>
      <Switch>
        <Route path="/list" render={() => <div>List</div>} />
        <Route path="/tasks" render={() => <div>Tasks</div>} />
        <Route path="/" render={() => <div>Home</div>} />
      </Switch>
    </main>
    <footer>Some Footer Thing</footer>
  </div>
);

(Also, just realised I used the sample codeSandbox you gave as a model - not your Qs – sorry about that.)


Another way of thinking about the structure would be to follow your very direct and good description a little more literally. You write that there should be first a registered-and-signed-in user, then the various routes of the site should appear after that is true. Rather than thinking of this as a routing question, think of it as a props question – is there a defined user props?, if so render the route's components, if not render the register/sign components. You could use a ternary to check for the existence of a user in the App, something like:

const App = ({user}) => (
  <div>
    { user ? Home : Signin }
  </div>
)

where Home would render your a version of your existing component and Signin would take you into a component similar to the App component. You could also leave the 'Not Found' route in App.

Upvotes: 1

Related Questions