React-router: How nested routes work?

TheSoul picture TheSoul · Jan 19, 2017 · Viewed 7.7k times · Source

I have a react application with react-router. I am trying to setup nested routes:

"/" --> home page
"/products" --> products list (child component of home page)
"/products/new" --> new product: child component of products list

What I tried to do so far:

<Route path="/" component="home" >

     <Route path="products" component="products" >

           <Route path="new" component="products_new" />
     </Route>

</Route>

Now in the browser from my default home page, when I hit "/products", the products component is loaded and I can see my list of products. But when I hit "products/new" nothing happens. I get a blank page. If I hit "/new" (not nested) it works (page product_new is loaded inside its parent). (this "/products/new" does not work; this "/new" works)

I had a look to this question on github Problem with nested routes #687. The solution says:

I discovered my problem. Parent routes are always called. Thats the intent. But child components need to have repeated <Router.RouteHandler/> to get rendered.

I cannot understand this solution. What does it mean: "but child components need to have repeated <Router.RouteHandler/> to get rendered"

EDIT1: Here are my components (routers and views):

  1. My routing hierarchy:

                <Route path="/" >    
                        <IndexRoute component={Home}/>
                        <Route path="products">
                            <IndexRoute component={Products} />
                            <Route path="new" component={Products_New} />
                        </Route>
                    </Route>                        
                </Router>
    
  2. My home component:

         <div className="col-lg-12">
            <h1>Home page</h1>
            <hr />
            {this.props.children}
        </div>
    
  3. My products components:

        <div>
            <div className="col-lg-12">
                <h1>Products</h1>
            </div>
            <hr />
            {this.props.children}
        </div>
    
  4. My product-new component:

enter image description here

Answer

Chris picture Chris · Jan 19, 2017

Have you had a look at IndexRoutes? If not, have a read about it on the official documentation. Your problem here is that when you visit /products/new, react-router tries to render all components that are above your products_new route.

This behavior is intended if you want to render a child component inside the parent component. Allow me to demonstrate with a couple of examples:

Example 1

Consider the following home component which has a Header and a Footer which is included on all pages.

<Header />
<div>{this.props.children}</div>
</Footer />

with the following routing:

<Route path="/" component={home} >
  <Route path="products" component={products} />
</Route>
  • Visiting / would render a page with just a <Header />, an empty <div> and <Footer />.
  • Visiting /products would render a page like above, but the <div> would now contain your <Products /> component.

Since, in your code you (probably) don't render {this.props.children} you will always get the parent <Home /> component, regardless if you visited / or /products.

This behavior is useful for stuff that wrap the main elements of your site, such as menus, banners, sidebars, etc.


Example 2

Now again consider the same home component:

<Header />
<div>{this.props.children}</div>
</Footer />

but with this routing:

<Route path="/">
  <IndexRoute component={home}
  <Route path="products" component={products} />
</Route>
  • Visiting / would render a page with just a <Header />, an empty <div> and <Footer />.
  • Visiting /products would now render your <Products /> component on its own, without being wrapped inside the parent <Home /> component.

TL;DR

If you instead want each route to render individual components, and not everything under the tree of that route, you should use the following routing instead:

const browserHistory = useRouterHistory(createHistory)({basename: '/'});

ReactDOM.render(
  <Route history={browserHistory}>
    <Route path="/">
      <IndexRoute component={home} />
      <Route path="products" >
        <IndexRoute component={products} />
        <Route path="new" component={products_new} />
      </Route>
    </Route>
  </Router>,
  document.getElementById('content')
);