20

If I have the following:

<Route path="/" component={Containers.App}>

  { /* Routes that use layout 1 */ }
  <IndexRoute component={Containers.Home}/>
  <Route path="about" component={Containers.About}/>
  <Route path="faq" component={Containers.Faq}/>
  <Route path="etc" component={Containers.Etc}/>

  { /* Routes that use layout 2 */ }
  <Route path="products" component={Containers.Products}/>
  <Route path="gallery" component={Containers.Gallery}/>
</Route>

How can I make it so that the two sets of routes each use a different layout.

If I only had a single layout then I would put it in App, but in this case where do I define the layout?

To make it even more complicated some of the layout components (eg top nav) are shared between both layout types.

SystemicPlural
  • 4,979
  • 7
  • 42
  • 64

6 Answers6

31

You can use routes without a path to define containers that are not defined by the url:

<Route path="/" component={Containers.App}>

  { /* Routes that use layout 1 */ }
  <Route component={Containers.Layout1}>
    <IndexRoute component={Containers.Home}/>
    <Route path="about" component={Containers.About}/>
    <Route path="faq" component={Containers.Faq}/>
    <Route path="etc" component={Containers.Etc}/>
  </Route>

  <Route component={Containers.Layout2}>
    { /* Routes that use layout 2 */ }
    <Route path="products" component={Containers.Products}/>
    <Route path="gallery" component={Containers.Gallery}/>
  </Route>
</Route>

The layout components can then import additional components such as the top nav

mojo
  • 684
  • 5
  • 14
SystemicPlural
  • 4,979
  • 7
  • 42
  • 64
  • 1
    Can you make a gist with your Layout1 and Layout 2 ? It will be helpful. Thks – Pintouch Mar 15 '16 at 09:56
  • You just need to be calling {this.props.children} somewhere in your render function. This is a presentational component – Rhys Bartels-Waller Jul 13 '16 at 09:26
  • @SystemicPlural the mentioned github repo not exist anymore , any alternative repo available ? – Developer Oct 04 '16 at 06:44
  • @RameshKumarThiyagarajan here is a link to another shared root example on the React repo https://github.com/ReactTraining/react-router/tree/master/examples/auth-with-shared-root – Sagebrush GIS Jan 22 '17 at 18:58
  • Does this method no longer work? All given links are dead and I cannot get this to function as intended in my project. – foxtrotuniform6969 Apr 23 '17 at 22:11
  • Soo... here's what I found out (using Meteor Base by The Meteor Chef FYI): I moved the route that I wanted to use a different layout than my normal one to be above my other routes in `routes.js`. After that, everything worked perfectly. For some reason the route I was trying to get to was giving me a 404 otherwise. – foxtrotuniform6969 Apr 23 '17 at 22:20
  • If I want Layout 1 to load js and css at header or footer different from Layout 2. How to do it? – Panup Pong Jul 04 '18 at 09:27
12

Route's path property has accepted an array of strings for a while now. See https://github.com/ReactTraining/react-router/pull/5889/commits/4b79b968389a5bda6141ac83c7118fba9c25ff05

Simplified to match the question routes, but I have working multiple layouts essentially like this (using react-router 5):

<App>
  <Switch>
    <Route path={["/products", "/gallery"]}>
      <LayoutTwo>
        <Switch>
          <Route path="/products" component={Products} />
          <Route path="/gallery" component={Gallery} />
        </Switch>
      </LayoutTwo>
    </Route>
    {/* Layout 1 is last because it is used for the root "/" and will be greedy */}
    <Route path={["/about", "/faq", "/etc", "/"]}>
      <LayoutOne>
        <Switch>
          <IndexRoute component={Home} />
          <Route path="/about" component={About} />
          <Route path="/faq" component={Faq} />
          <Route path="/etc" component={Etc} />
        </Switch>
      </LayoutOne>
    </Route>
  </Switch>
</App>

This solution prevents re-mounting the layouts on route changes, which can break transitions, etc.

markt
  • 4,821
  • 26
  • 25
9

Here's a great way to use multiple layouts with different React components.

In your router you can use:

<Router history={browserHistory}>
  <Route component={MainLayout}>
    <Route path="/" component={Home} />
    <Route path="/about" component={About} />
  </Route>
  <Route component={EmptyLayout}>
    <Route path="/sign-in" component={SignIn} />
  </Route>
  <Route path="*" component={NotFound}/>
</Router>

enter image description here

Source: https://sergiotapia.me/different-layouts-with-react-router-71c553dbe01d

Sergio Tapia
  • 6,791
  • 6
  • 30
  • 48
  • This is basically same as [decorators](https://babeljs.io/docs/en/babel-plugin-transform-decorators/) – vsync Sep 15 '18 at 08:06
  • What if the `MainLayout` has `header` & `footer` and you want to stick your Route just **between** them? – vsync Sep 15 '18 at 08:44
  • @vsync Then you make sure to render `{this.props.children}` between the `header` and the `footer` in the `MainLayout` component. – Hein Haraldson Berg Nov 17 '19 at 17:45
5

Pintouch, I was able to get this working with the following example:

Layout1:

import React from 'react'

const Layout1 = (props) => (
    <div>
        <h1>Layout 1</h1>
        {props.children}
    </div>
)

export default Layout1

Layout2:

import React from 'react'

const Layout2 = (props) => (
    <div>
        <h1>Layout 2</h1>
        {props.children}
    </div>
)

export default Layout2

Layout Container:

import React from 'react'

const LayoutContainer = (props) => (
    <div>
                {props.children}
    </div>
)

export default LayoutContainer

Routes:

import React from 'react';
import { Router, Route, IndexRoute, hashHistory } from 'react-router';

import LayoutContainer from './LayoutContainer'
import Layout1 from './Layout1'
import Layout2 from './Layout2'
import ContactManagerView from './ContactManagerView'
import CallerIdView from './CallerIdView'
import NotFound from './NotFound'

<Router history={hashHistory}>
    <Route path="/" component={LayoutContainer}>
        <Route component={Layout1}>
            <IndexRoute component={DashboardView}/>
            <Route path='Contacts' component={ContactManagerView}/>
        </Route>

        <Route component={Layout2}>
            <Route path='CallerId' component={CallerIdView}/>
        </Route>

        <Route component={Layout}>
            <Route path='*' component={NotFound}/>
        </Route>
    </Route>
</Router>
Sean Siegel
  • 59
  • 1
  • 1
  • A+ for completeness. – Fydo Apr 14 '17 at 14:10
  • 4
    Seems like React does not like this: *`Warning: You should not use and in the same route; will be ignored`* - [solution in this post](https://stackoverflow.com/q/42095600/104380) – vsync Sep 15 '18 at 08:49
1

I came across this question and found a solution that I want to share.

With react router v4 we could render the routes directly in your layout. Which is more readable and easy to maintain.

Layout

export class MainLayout extends React.PureComponent {
  render() {
    return (
      <div>
        <Header />
        {this.props.children}
        <Footer />
      </div>
    );
  }
}

Mainlayout.propTypes = {
  children: PropTypes.node.isRequired,
}

Router

export default function App() {
  return (
    <Switch>
      <MainLayout>
        <Switch>
          <Route path="/" component={Home} />
          <Route path="/about" component={About} />
        </Switch>
      </MainLayout>
      <OtherLayout>
        .... other paths
      </OtherLayout>
    </Switch>
  );
}
Lucas K
  • 72
  • 1
  • 4
1

You could create a function RouteWithLayout that renders the <Route> wrapped within the layout:

const RouteWithLayout = ({ component: Component, layout: Layout, ...rest }) => (
  <Route {...rest} render={props => (
    <Layout>
      <Component {...props} />
    </Layout>
  )} />
)

const MainLayout = props => (
  <div>
    <h1>Main</h1>
    {props.children}
  </div>
)

const AltLayout = props => (
  <div>
    <h1>Alt</h1>
    {props.children}
  </div>
)

const Foo = () => (
  <p>Foo</p>
)

const Bar = () => (
  <p>Bar</p>
)

const App = () => (
  <div>
    <Switch>
      <RouteWithLayout exact path="/foo" layout={MainLayout} component={Foo} />
      <RouteWithLayout exact path="/bar" layout={AltLayout} component={Bar} />
    </Switch>
  </div>
)
Agu Dondo
  • 9,636
  • 6
  • 50
  • 59