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

[Question] Pass props from page to layout #2112

Closed
davidhouweling opened this issue Sep 14, 2017 · 27 comments
Closed

[Question] Pass props from page to layout #2112

davidhouweling opened this issue Sep 14, 2017 · 27 comments
Labels
type: question or discussion Issue discussing or asking a question about Gatsby

Comments

@davidhouweling
Copy link

I understand that the purpose of layouts is that it should be shared across all the pages. But there needs to be a way to send customisations from a page itself to the layout so you can do things like highlight certain elements in the header for example.

At the moment the only way is to embed the page variations into the layout itself and check the location.pathname which really isn't appropriate.

@KyleAMathews
Copy link
Contributor

You could pass a prop to the pages that you could call from them e.g.

render () {
  return <div>
    { this.props.children({...this.props, updateLayoutFunction }) }
  </div>
}

You'd then update the layout state and re-render.

@davidhouweling
Copy link
Author

Whilst that does work it definitely isn't clean... and close to an anti-pattern...

@KyleAMathews
Copy link
Contributor

Layout components are the parents to page components so not sure why you think pages should pass props to layouts.

@davidhouweling
Copy link
Author

I'm attempting multilingual support, so being able to pass certain pieces such as the different translations would be very helpful.

@abumalick
Copy link
Contributor

abumalick commented Sep 19, 2017

I am also interested to pass a context to layouts, I would pass it from gatsby-node.js though. It is useful for multilingual website: you pass the language, so you can query the appropriate strings in the layout with graphql.

We could use multiple layouts files like here: #1895
But it would be more simple passing a simple context to it.
Is it possible to do that @KyleAMathews ?

@KyleAMathews
Copy link
Contributor

yeah, when you pass context when you create a layout https://www.gatsbyjs.org/docs/bound-action-creators/#createLayout

@abumalick
Copy link
Contributor

abumalick commented Sep 23, 2017 via email

@davidhouweling
Copy link
Author

@KyleAMathews at that point, is it aware of the path though? I take it probably not?

@KyleAMathews
Copy link
Contributor

Which point? When creating a layout? No — you'd need to pass it data keyed by path.

Easier probably to use the "parent passes a callback function to child" pattern though.

@davidhouweling
Copy link
Author

@KyleAMathews yea i assumed so. We've already began to work towards that approach.

@yasserzubair
Copy link

@KyleAMathews @davidhouweling I have a header component in the layout, and when clicked on the labels on the header inside the layout, I want the page to scroll, for this, I need to pass some data to the header component inside the layout. Please guide me through it. Thanks.

@mr2day
Copy link

mr2day commented Oct 16, 2017

I'm trying to pass props to children like this:

render () {
return


{ this.props.children({...this.props, updateLayoutFunction }) }

}

however, I get the error that updateLayoutFunction is not defined, no matter where and how I try to define it. Could you please tell me how can it be defined?

@piducancore
Copy link
Contributor

piducancore commented Oct 16, 2017

assuming updateLayoutFunction is a method of parent component, you can do something like this:

render() {

  const updateLayoutFunction = this.updateLayoutFunction

  ...

  { this.props.children({...this.props, updateLayoutFunction }) }

}

@mr2day
Copy link

mr2day commented Oct 17, 2017

Thanks, man. You saved my life.

@davidhouweling
Copy link
Author

One thing to keep in mind with the approach, it only appears to render the update to the layout on the client side. It does not put it into the statically generated html file. As a result I moved away from using the layout solution all together. We ended up creating a component which is applied via the page template as a root component of the template.

@mr2day
Copy link

mr2day commented Oct 23, 2017

Thank you for the information. I didn't test it on the statically generated site. It seems that I will have to move away from the layout solution too.

@laij84
Copy link

laij84 commented Oct 23, 2017

I have a state variable on the template component which is passed as props to the template children, however when the state is updated, the props that is passed to the children does not update? Is this a bug? The only way I could get this to work is by setting the state variable as a key property on the template component, so when it changes it forces react to unmount and and mount the template again updating the props that is passed to the children. Anyone else got a solution for this?

@arnaslu
Copy link

arnaslu commented Oct 28, 2017

@davidhouweling can you share your solution?

@cmalla94
Copy link

I need to pass the state from index.js to submit.js. The state contains a user input which is an ID required to make an API call in the submit.js. How do I pass the state from index.js to submit.js?

@cmalla94
Copy link

<Link to="/submit" params={{value: this.state.value}} onClick={this.handleSubmit}> Submit </Link>

@abumalick
Copy link
Contributor

abumalick commented Dec 19, 2017

import submit from './submit.js'
...
handleClick = () => {
  const {value} = this.state
  submit(value)
}
...
<button onClick={handleClick}>Submit</button>

@fk fk added the type: question or discussion Issue discussing or asking a question about Gatsby label Feb 14, 2018
@rockmandash
Copy link

I think you should add layout: false

render () {
  return <div>
    { this.props.children({...this.props, layout: false, updateLayoutFunction }) }
  </div>
}

@decimoseptimo
Copy link

/layouts/index.js lacks the functionailty required by the OP. Which is likely a valid use-case.

In order to make this work, component composition can be implemented as an alternative solution.

1)Create a layout at /components/layout.js with the component:

export default ({aPageProp, children}) => (
<div>
    {aPageProp}
    header
    {children}
    footer
</div>
);

2)Create a page at /pages/page.js with the component:

export default () => (
        <Layout aPageProp="value">
            Page content
        </Layout>
);

@RobinHerzog
Copy link

Anyone has a solution ?
I would to pass variables to layout for https://moz.com/learn/seo/hreflang-tag

@abumalick
Copy link
Contributor

@KyleAMathews
Copy link
Contributor

Due to the high volume of issues, we're closing out older ones without recent activity. Please open a new issue if you need help!

@sekoyo
Copy link

sekoyo commented Jan 28, 2020

I had a similar issue where I moved my base component <Application> which was doing some basic setup like meta tags, global styles, top navigation with theme switching, setting the title etc

This was easy to render as the root component of my various page layouts and pass whatever props I wanted into it (like page title) but then it became apparent than when switching routes everything was being unmounted and re-rendered, causing the state in Application to be lost (e.g. the currently chosen theme).

There is a section: https://www.gatsbyjs.org/docs/layout-components/#how-to-prevent-layout-components-from-unmounting

But it gives a solution without mentioning how to communicate between this component and the rest of the app. The element that is passed in here is not a function it's a ReactElement and so I can't pass in props from Application to it with "render props" as suggested.

I've ended up doing this but not sure if I haven't understood Gatsby properly as it feels like a hacky solution to something that everyone must come across when making an app:

export const wrapPageElement = ({ element, props }) => {
  return (
    <Application {...props}>
      {(state, dispatcher) =>
        React.Children.map(element, child => React.cloneElement(child, { state, dispatcher }))
      }
    </Application>
  );
};

Maybe a better solution will be to move the state and dispatcher to a Context if this is really the way to do it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
type: question or discussion Issue discussing or asking a question about Gatsby
Projects
None yet
Development

No branches or pull requests