React router, How to render component within a layout (vs. stand alone)

I have my meteor app working with React and React-Router. I am able to set up routes and render them correctly. My issue is I would like to render a component inside a layout (common grid, menu, header, etc). Right now, any path renders the components by taking over the entire page.

main.html

<head>
  <title>List App</title>
</head>

<body>
  <div id="target"></div>
</body>

main.coffee

browserHistory = createBrowserHistory()

export renderRoutes = () => (
  <Router history={browserHistory}>
    <Switch>
      <Route exact path='/' component={App} />
      <Route path="/mylists" component={MyLists} />
      <Route path="/list/:listId" component={List} />
      <Route path="/layout" component={Layout} />
      <Route exact path="/discover" component={Browse} />
      <Route component={NotFoundPage} />
    </Switch>
  </Router>
)

Meteor.startup () ->
  console.log "Hello from Client."
  render(renderRoutes(), document.getElementById('target'))

When the browser navigates to a route (ie. path=’/’) then the browser displays ONLY that single component, in this case, App. Makes perfect sense as I am rendering all the routes to target.

I am trying to get a route to render INSIDE another component. For example, I want a layout component with nav, logo, etc. Once a user hit’s a route, I’d want just that component rendered inside the layout (ie. MyLists, Browse, etc). Seems like it should be straightforward, but I can’t seem to get set it up right.

Appreciate any advice.

So this is really not a meteor question but a react-router question, and it depends on which version of RR you’re using. If it’s RR V4, you can do it like so:

// Just to make sure you're importing BrowserRouter
import { BrowserRouter, Route, Switch } from "react-router-dom";

...


  <BrowserRouter>
    <div>
      <Header />
      <Menu />
      // ... other constantly shown components
      // then, put your route specific components inside the switch:
      <Switch>
        <Route exact path='/' component={App} />
        <Route path="/mylists" component={MyLists} />
        <Route path="/list/:listId" component={List} />
        <Route path="/layout" component={Layout} />
        <Route exact path="/discover" component={Browse} />
        <Route component={NotFoundPage} />
      </Switch>
    </div>
  </BrowserRouter>

Based on how you have it set up though I think you might be using V3. If you are, I would recommend upgrading. If you really don’t want to, you can make it work like this

const App = props => (
  <div>
    <Header />
    <Menu />
    // ... other constantly shown components
    {props.children} // route-specific components will appear here
</div>
);

// and then modify your renderRoutes to be like:

export renderRoutes = () => (
  <Router history={browserHistory}>
    <Switch>
      <Route path='/' component={App} /> // this uses the App component we made above. All below components will appear there as `props.children` when their route is selected
      <IndexRoute component={IndexRouteComponent} /> // The component to send to App's `props.children` when route is '/'
      <Route path="/mylists" component={MyLists} />
      <Route path="/list/:listId" component={List} />
      <Route path="/layout" component={Layout} />
      <Route exact path="/discover" component={Browse} />
      <Route component={NotFoundPage} />
    </Switch>
  </Router>
)
1 Like

I’m on RR4, so this worked great. Thanks for the help!!

Also, RR4 render function allows you to render whatever you want within a route including logic to determine that: https://reacttraining.com/react-router/web/api/Route/render-func

Thanks to everyone for the replies. Apologies for my novice React Router questions, but I think I am close.

I have the layout showing up correctly, but the navigation, when clicked, changes the browser bar location address, but does not reload the content. In essence, I can click around my and the links change to active color (provided by antD), but the content won’t change unless I reload the page manually.

export renderRoutes = () => (
 <div>
    <Layout className="layout">
      <Header style={{ position: 'fixed', zIndex: 1, width: '100%' }}>
        <div className="logo">
          <img width={125} height={40} alt="logo" src="../imports/images/logo.png" />
        </div>
        <BrowserRouter>
          <NavBar />
        </BrowserRouter>
      </Header>
      <Content style={{ padding: '0 50px' }}>
        <div style={{ background: '#fff', padding: 24, minHeight: 768 }}>
         <BrowserRouter>
            <div>
              <Switch>
                <Route exact path='/' component={App} />
                <Route path="/mylists" component={MyLists} />
                <Route path="/list/:listId" component={List} />
                <Route path="/layout" component={Layout} />
                <Route exact path="/discover" component={Browse} />
                <Route component={NotFoundPage} />
              </Switch>
            </div>
          </BrowserRouter>
        </div>
      </Content>
      <Footer style={{ textAlign: 'center' }}>
        BetterList //  ©2018
      </Footer>
    </Layout>
  </div>
)

Meteor.startup () ->
  console.log "Hello from Client."
  render(renderRoutes(), document.getElementById('target'))

My NavBar component is…

export default class NavBar extends Component

  render: () ->
    (<Menu
        mode="horizontal"
        defaultSelectedKeys={['1']}
        style={{ lineHeight: '48px' }}
      >
        <Menu.Item key="1"><Link to="/">Home</Link></Menu.Item>
        <Menu.Item key="2"><Link to="/discover">Discover</Link></Menu.Item>
        <Menu.Item key="3"><Link to="/mylists">My Lists</Link></Menu.Item>
      </Menu>
    )

Try getting rid of the two <BrowserRouter>s that you have and replace the outermost <div> with a <BrowserRouter>

1 Like