Reusing layouts in React Router 4

In a React Router application it’s very common to want to render a default set of components on every route, such as a header and footer:

<div className="App">
  <div className="Header">
    Page Header
  </div>
  <div className="Content">
    {content that changes}
  </div>
  <div className="Footer">
    Page Footer
  </div>
</div>

In the latest version of React Router it is very easy to achieve this, as well as creating child layouts for specific use cases.

Creating a default layout

The default layout is where components used on every page of our app will exist. React router offers a render prop which will be called when the route matches:

// The usual way to render a pre-defined component
<Route path="/" component={SomeComponent} />

// Using the render prop allows a more manual setup
<Route path="/" render={matchProps => <SomeComponent {...matchProps} />} />

This is useful because we can wrap a component around the <Route /> and control where our component is rendered whilst allowing all the usual props to be Route get passed through:

const DefaultLayout = ({component: Component, ...rest}) => {
  return (
    <Route {...rest} render={matchProps => (
      <div className="DefaultLayout">
        <div className="Header">Header</div>
          <Component {...matchProps} />
        <div className="Footer">Footer</div>
      </div>
    )} />
  )
};
<DefaultLayout path="/" component={SomeComponent} />

The rest parameter will contain every prop passed to DefaultLayout except for component so it allows us to ‘forward’ them on to the underlying Route component as usual.

By supplying a render prop to the Route we can control where the component prop is rendered. In this case we wrap it in some HTML that contains a header and footer but this could just easily be a group of other components. The matchProps are what usually get passed to a component rendered by Route.

It’s important to rename the component prop to Component with de-structuring as it effects how JSX transforms our code:

<component />
// becomes
React.createElement("component", null); // Not what we wanted

<Component />
// becomes
React.createElement(Component, null); // Now it knows we meant a React component

Read the documentation for more information.

Extending the default layout

Whilst our default layout will contain components shared on every page there might be times when we want to add certain components for one view, like a blog post for example. A way to solve this is to build upon the DefaultLayout and then add the shared components just for the new view:

const PostLayout = ({component: Component, ...rest}) => {
  return (
    <DefaultLayout {...rest} component={matchProps => (
      <div className="Post">
        <div className="Post-content">
          <Component {...matchProps} />
        </div>
        <div className="Post-aside">
          <SomeSideBar />
        </div>
      </div>
    )} />
  );
};
<PostLayout path="/posts/:post" component={PostComponent} />

The only difference is to pass a function to the component prop instead of the render prop. Otherwise you’re free to extend layouts as many times as needed.

That’s it

Short and sweet. The new version of React Router focuses on working with the React component model and allows simple patterns like this to shine through.

Check out this GitHub issue to read discussion around using different default layouts.