Gatsby: Accessing to the current pathContext from layout

Created on 9 Jan 2018  Â·  10Comments  Â·  Source: gatsbyjs/gatsby

Hi,

I'm passing some data to my pages by the context option during the onCreatePage action (gatsby-node.js). This data is available in the pages from this.props.pathContext, but sometimes I need this data from the layouts.

I have two solutions in my mind:

1) Declare a method in the layout that update its state, then pass it to the child (= page). At last, use it to pass the data from the page to the layout state.

2) Use a GraphQL query to access the page context from the layout, but I really don't understand how to query the "current page" from the layout.

// My try so far, but I don't know how to make the "currentId" dynamic)
export const pageQuery = graphql`
  query LayoutProjectsIndex($path: String!) {
     sitePage(id : { eq: $currentId }) {
        path
        context {
          language
          foo
        }
    }
  }
`;

The second option seems more clean & less odd. What's your opinion?

Most helpful comment

Yeah, layout wrapper component is way better when you can. We're actually removing our special layout components in v2 as @pieh mentioned for this and a bunch of other reasons — https://github.com/gatsbyjs/gatsby/issues/3830

All 10 comments

I'm having the same problem at the moment.
Is there a good solution to access page information (from graphql) in the layouts?

cc @KyleAMathews @pieh do you have any thoughts on this?

@monsieurnebo Your second idea wouldn't work because of current design as then your layout would need to be duplicated for all your pages - so for this it would be easier just to not use layouts at all and in your page components wrap your page "manually" in layout component:

const page = ({data}) => {
  return (
    <LayoutThatWrapsPages pageData={data}>
      { // your actual page components }
    </LayoutThatWrapsPages>
  )
}

Which is actually something that is desired to be able to do this way (and remove gatsby specific layouts construct) - https://github.com/gatsbyjs/gatsby/issues/3830#issuecomment-363326165 - "What do we want" section. Just currently it's not viable to do - current chunk splitting would or could cause some load penalties in some cases and You would have to declare your global queries in each of your pages and that would cause data replication and heavier loads.

Your first option is probably better for now, just I'm not sure if you can pass props to page component from layout. Would have to check that.

@pieh I ended up using a "LayoutThatWrapsPages" in the meantime. It's working fine but I'm effrayed by the potential performances cost (I didn't dig up to verify this so far).

If you have single layout it shouldn't be too bad - layout should be then placed in common chunk by webpack (along with react react-router etc - stuff that is used on every page). If you would have multiple layouts this might have more load consequences, but for single layout this should be fine (but I'm not 100% sure on that, actual build process is something I have yet to explore in depth, so probably would have to wait for @KyleAMathews comment on this)

You can check https://www.npmjs.com/package/source-map-explorer to see if layout module is bundled in common chunk or in your templates chunk

Yeah, layout wrapper component is way better when you can. We're actually removing our special layout components in v2 as @pieh mentioned for this and a bunch of other reasons — https://github.com/gatsbyjs/gatsby/issues/3830

My website is having a modular layout. The full version of the layout is the following one:

BREADCRUMB
{page}
SECTION FOO
SECTION BAR
FOOTER
LEGAL

The header & breadcrumb are always visibles and displaying some page-related information (e.g. page title), and the other sections don't appear on every single page. I created a layout component displaying these section (or not) according to the props passed from the page.

PageLayout

export default class PageLayout extends React.PureComponent {

  static propTypes = {
    headerTitle  : PropTypes.string.isRequired,
    breadcrumb : PropTypes.string,
    sectionFoo : PropTypes.bool,
    sectionBar : PropTypes.bool,
    footer : PropTypes.bool,
    legal : PropTypes.bool
  };

  // ...

 render() {
    const { headerTitle, breadcrumb, sectionFoo, sectionBar, footer, legal, children } = this.props;

    return (
      <div>
        <Header title={headerTitle} />
        {breadcrumb && this.renderBreadcrumb(breadcrumb)}
        <main>
          {children}  // page
        </main>
        {sectionFoo && this.renderSectionFoo()}
        {sectionBar && this.renderSectionBar()}
        {footer && this.renderFooter()}
        {legal && this.renderLegal()}
      </div>
    );
  }

}

Example Page

In this case, we only want to display the sectionFoo and footer layout sections, but not the sectionBar and legal ones.

export default class ExamplePage extends React.PureComponent {

  render() {
    return (
      <PageLayout
        headerTitle="My amazing page"
        breadcrumb="Breadcrumb blabla"
        sectionFoo
        footer
      >
        <div>
          // The page content
        </div>
      </PageLayout>
    );

  }

}

@KyleAMathews @pieh Does it sound right to you guys ?

Yeah, this seems like it'd work great!

Hello, I have the same problem for page name in header. I've tried to do wrapper for layout and nothing's changed. @monsieurnebo, I don't understand your example above. What will I do? My layout:

import Helmet from "react-helmet";
import PropTypes from "prop-types";
import React from "react";
import withRoot from "../withRoot";
import { withStyles } from "material-ui/styles";

import Footer from "../components/Footer";
import Header from "../components/Header";
import Sidebar from "../components/Sidebar";

import "typeface-roboto";

const drawerWidth = 240;

const styles = theme => ({
  root: {
    flexGrow: 1,
    zIndex: 1,
    overflow: "hidden",
    position: "relative",
    display: "flex",
    width: "100%"
  },
  toolbar: theme.mixins.toolbar,
  content: {
    width: "100%"
  },
  wrapper: {
    display: "flex",
    width: "100%",
    flexFlow: "column nowrap",
    justifyContent: "center",
    alignItems: "center"
  }
});

class IndexLayout extends React.Component {
  constructor(props) {
    super(props);

    this.state = {
      mobileOpen: false
    };
  }

  handleDrawerToggle = () => {
    this.setState({ mobileOpen: !this.state.mobileOpen });
  };

  render() {
    const {
      children,
      classes,
      data: { site: { siteMetadata: { description, keywords, title } } }
    } = this.props;
    const { mobileOpen } = this.state;

    return (
      <div className={classes.root}>
        <Helmet
          title={`${title} — ${description}`}
          meta={[
            {
              name: "description",
              content: description
            },
            { name: "keywords", content: keywords }
          ]}
        />
        <Sidebar
          handleDrawerToggle={this.handleDrawerToggle}
          mobileOpen={mobileOpen}
          {...this.props}
        />
        <div className={classes.wrapper}>
          <Header
            handleDrawerToggle={this.handleDrawerToggle}
            {...this.props}
          />
          <main className={classes.content} id="main">
            <div className={classes.toolbar} />
            {children({ ...this.props })}
          </main>
          <Footer {...this.props} />
        </div>
      </div>
    );
  }
}

IndexLayout.propTypes = {
  children: PropTypes.func.isRequired,
  classes: PropTypes.object.isRequired,
  data: PropTypes.object.isRequired
};

export const IndexLayoutQuery = graphql`
  query IndexLayoutQuery {
    lessons: allMarkdownRemark(
      sort: { fields: [frontmatter___title], order: ASC }
    ) {
      edges {
        node {
          fields {
            slug
          }
          frontmatter {
            chapter
            cover
            date
            lesson
            title
            type
          }
          id
        }
      }
    }
    site {
      siteMetadata {
        description
        pathPrefix
        rootDir
        shortTitle
        title
        titleAlt
        url
        user {
          description
          location
          name
          site
        }
      }
    }
  }
`;

export default withRoot(withStyles(styles, { withTheme: true })(IndexLayout));

The layout component called inside pages is now the default behavior of Gatsby V2. I'm closing this.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

Oppenheimer1 picture Oppenheimer1  Â·  3Comments

signalwerk picture signalwerk  Â·  3Comments

KyleAMathews picture KyleAMathews  Â·  3Comments

hobochild picture hobochild  Â·  3Comments

theduke picture theduke  Â·  3Comments