9

I'm pulling my hair out trying to render multiple layouts with React Router v4.

For instance, I'd like pages with the following paths to have layout 1:

  • exact path="/"
  • path="/blog"
  • path="/about"
  • path="/projects"

and the following paths to have layout 2:

  • path="/blog/:id
  • path="/project/:id

Effectively what's being answered here but for v4: Using multiple layouts for react-router components

Community
  • 1
  • 1
redstripepapi
  • 111
  • 1
  • 4
  • you will need to create a logic on top of these routes to handle which layout to display based on the path. then add the routes to that layout and render – Rei Dien Apr 11 '17 at 06:58
  • Would be really helpful if you could submit some code demonstrating how this is achieved. @ReiDien – redstripepapi Apr 11 '17 at 08:11

4 Answers4

10

None of the other answers worked so I came up with the following solution. I used the render prop instead of the traditional component prop at the highest level.

It uses the layoutPicker function to determine the layout based on the path. If that path isn't assigned to a layout then it returns a "bad route" message.

import SimpleLayout from './layouts/simple-layout';
import FullLayout from './layouts/full-layout';

var layoutAssignments = {
  '/': FullLayout,
  '/pricing': FullLayout,
  '/signup': SimpleLayout,
  '/login': SimpleLayout
}

var layoutPicker = function(props){
  var Layout = layoutAssignments[props.location.pathname];
  return Layout ? <Layout/> : <pre>bad route</pre>;
};

class Main extends React.Component {
  render(){
    return (
      <Router>
        <Route path="*" render={layoutPicker}/>
      </Router>
    );
  }
}


simple-layout.js and full-layout.js follow this format:

class SimpleLayout extends React.Component {
  render(){
    return (
      <div>
        <Route path="/signup" component={SignupPage}/>
        <Route path="/login" component={LoginPage}/>
      </div>
    );
  }
}
spencer.sm
  • 14,681
  • 8
  • 67
  • 76
  • Before anyone goes down this route, it becomes difficult when you want to use wildcard paths, e.g. '/markets/:id' I'm sure there's a way to finagle it but I couldn't figure it out within a few minutes so I went with Nikolay's answer below – Will May 07 '19 at 23:19
8

So, for this you should use render function (https://reacttraining.com/react-router/core/api/Route/render-func)

A really good article that helped me: https://simonsmith.io/reusing-layouts-in-react-router-4/

In the end you will be use something like this:

<Router>
 <div>
  <DefaultLayout path="/" component={SomeComponent} />
  <PostLayout path="/posts/:post" component={PostComponent} />
 </div>
</Router>
Nikolai Novikov
  • 241
  • 1
  • 5
1

I solved this problem utilizing a bit of both of your solutions:

My Routes.js file

import BaseWithNav from './layouts/base_with_nav';
import BaseNoNav from './layouts/base_no_nav';

function renderWithLayout(Component, Layout) {
  return <Layout><Component /></Layout>
}

export default () => (
  <Switch>
    {/* Routes with Sidebar Navigation */}
    <Route exact path="/" render={() => renderWithLayout(Home, BaseWithNav)} />

    {/* Routes without Sidebar Navigation */}
    <Route path="/error" render={() => renderWithLayout(AppErrorMsg, BaseNoNav)} />
    <Route path="/*" render={() => renderWithLayout(PageNotFound, BaseNoNav)} />
  </Switch>
)

Base.js (where routes get imported)

export default class Base extends React.Component {
  render()  {
    return (
      <Provider store={store}>
        <Router>
          <Routes />
        </Router>
      </Provider>
    )
  }
}

Layouts

BaseWithNav.js

class BaseWithNav extends Component {
  constructor(props) {
    super(props);
  }

  render() {        
    return <div id="base-no-nav">
      <MainNavigation />
      <main>
        {this.props.children}
      </main>
    </div>
  }
}

export default BaseWithNav;

BaseNoNav.js

class BaseNoNav extends Component {
  constructor(props) {
    super(props);
  }

  render() {
    let {classes} = this.props;

    return <div id="base-no-nav">
      <main>
        {this.props.children}
      </main>
    </div>
  }
}

export default BaseNoNav;

I hope this helps!

Sixers17
  • 510
  • 2
  • 4
  • 17
-1

I know i am replying late but it's easy to do that, i hope it will helps to newbie. i am using React 4

Layout.js

export default props => (
    <div>
        <NavMenu />
        <Container>
            {props.children}
        </Container>
    </div>
);

LoginLayout.js

export default props => (
    <div>
        <Container>
            {props.children}
        </Container>
    </div>
);

Now finally we have our App

App.js

function renderWithLoginLayout(Component, Layout) {
    return <LoginLayout><Component /></LoginLayout>
}

function renderWithLayout(Path, Component, Layout) {
    return <Layout><Route path={Path} component={Component} /></Layout>
}


export default () => (
    <Switch>
        <Route exact path='/' render={() => renderWithLayout(this.path, Home, Layout)} />
        <Route path='/counter' render={() => renderWithLayout(this.path, Counter, Layout)} />
        <Route path='/fetch-data/:startDateIndex?' render={() => renderWithLayout(this.path, FetchData, Layout)} />
        <Route path='/login' render={() => renderWithLoginLayout(Login, LoginLayout)} />
    </Switch>
);
saurav singh
  • 347
  • 5
  • 14