Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Accessing to the current pathContext from layout #3449

Closed
monsieurnebo opened this issue Jan 9, 2018 · 10 comments
Closed

Accessing to the current pathContext from layout #3449

monsieurnebo opened this issue Jan 9, 2018 · 10 comments

Comments

@monsieurnebo
Copy link
Contributor

monsieurnebo commented Jan 9, 2018

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?

@peXed
Copy link

peXed commented Feb 13, 2018

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

@m-allanson
Copy link
Contributor

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

@pieh
Copy link
Contributor

pieh commented Feb 13, 2018

@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) - #3830 (comment) - "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.

@monsieurnebo
Copy link
Contributor Author

monsieurnebo commented Feb 13, 2018

@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).

@pieh
Copy link
Contributor

pieh commented Feb 13, 2018

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

@KyleAMathews
Copy link
Contributor

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 — #3830

@monsieurnebo
Copy link
Contributor Author

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

HEADER
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 ?

@KyleAMathews
Copy link
Contributor

Yeah, this seems like it'd work great!

@itfarrier
Copy link

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));

@monsieurnebo
Copy link
Contributor Author

monsieurnebo commented Jul 3, 2018

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

6 participants