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

Creating 2 layouts from same component with different context #3025

Closed
abumalick opened this issue Nov 25, 2017 · 8 comments · Fixed by #3039
Closed

Creating 2 layouts from same component with different context #3025

abumalick opened this issue Nov 25, 2017 · 8 comments · Fixed by #3039

Comments

@abumalick
Copy link
Contributor

abumalick commented Nov 25, 2017

Hello,

I am developing a multilingual website and need to pass the current language to
the layout. For this I used createLayout:

const path = require(`path`)
const slash = require(`slash`)

const MainLayout = path.resolve(`./src/templates/MainLayout.jsx`)
const homeTemplate = path.resolve(`./src/templates/Home.jsx`)
const languagePath = {
  'ar-EG': '/',
  'fr-FR': '/fr/',
}
Object.keys(languagePaths).forEach((locale) => {
    createLayout({
    component: slash(MainLayout),
    id: `main-layout-${locale}`,
    context: {
        locale,
    },
    })
})

// home pages
Object.entries(languagePaths).forEach(([locale, languagePath]) => {
    createPage({
    path: languagePath,
    component: slash(homeTemplate),
    context: {
        languagePath,
        locale,
    },
    layout: `main-layout-${locale}`
    })
})

The problem is that when I log props.layoutContext.locale inside my
MainLayout, I always get fr-FR (on the arabic page and the french page)

While investigating, I could make it work when I duplicate my
template/MainLayout file, and use one file for each language (this is very very
ugly):

const MainLayout = path.resolve(`./src/templates/MainLayout.jsx`)
const MainLayout2 = path.resolve(`./src/templates/MainLayout2.jsx`)
// layout for each lang
Object.keys(languagePaths).forEach((locale, i) => {
    createLayout({
    component: slash(i === 1 ? MainLayout2 : MainLayout),
    id: `main-layout-${locale}`,
    context: {
        locale,
    },
    })
})

After that I tried to debug it in gatsby, cloned the repo, and configured
redux-devtools.

While examining the store, I noticed that the layouts are created correctly with
the good context for each layout, and that pages are also created correctly,
with their corresponding layout id.

I am a bit stuck because redux-tools resets its history while building, so I
cannot look at the actions creating the pages to try and debug more.

Can you give me any indication to how to investigate further ?

@abumalick
Copy link
Contributor Author

I am using last version of gatsby

@yachaka
Copy link

yachaka commented Nov 26, 2017

Exact same problem here.
The only context picked up is the last one

@yachaka
Copy link

yachaka commented Nov 26, 2017

Problem comes from

` "${l.componentChunkName}": preferDefault(require("${

Layout are cached along their context, but the identifier for them only comes from the component path. So there can't be multiple contexts for the same component path.
Identifier should be changed to an other thing or otherwise can stop caching layouts, as it is done with components

@yachaka
Copy link

yachaka commented Nov 26, 2017

As a temporary solution, I wrote down the layouts file manually.

@abumalick
Copy link
Contributor Author

thanks @yachaka ,
it seems you are right, here is the next step in layout creation:

const layout = syncRequires.layouts[page.layoutComponentChunkName]

I will try to fix it.

KyleAMathews pushed a commit that referenced this issue Nov 27, 2017
…ext (#3039)

The bug appears in pages-writer.js:89 , we need to use the layout id as identifier instead of his path

fixes #3025
@danjebs
Copy link

danjebs commented Jan 12, 2018

@abumalick @yachaka curious why you created separate layouts - is there no way to pass the alternate language versions from the page up to the layout?

I'm attempting a similar task, although my slugs are also translated. So if I were to create separate layouts then I'd end up with one for every page-language combination on the site and that seems suboptimal.

@abumalick
Copy link
Contributor Author

It is not nice to pass data from the page to the layout in my opinon, it is like passing data from a child to a parent when we can pass the data in the normal way.

I prefer creating a layout for each language. All pages of one language will use the same layout. Here is some code:

gatsby-node.js :

const _ = require(`lodash`)
const Promise = require(`bluebird`)
const path = require(`path`)
const slash = require(`slash`)

exports.createPages = ({graphql, boundActionCreators}) => {
  const {createLayout, createPage} = boundActionCreators
  return new Promise((resolve, reject) => {
    // We get the language path

    graphql(
      `
        {
          allContentfulWebsite(limit: 1000) {
            edges {
              node {
                languageName
                languagePath
                node_locale
              }
            }
          }
        }
      `,
    ).then((languagesRes) => {
      if (languagesRes.errors) {
        reject(languagesRes.errors)
      }

      const MainLayout = path.resolve(`./src/templates/MainLayout.jsx`)

      const homeTemplate = path.resolve(`./src/templates/Home.jsx`)

      // we store the list of languages with their path
      const languagePaths = {}
      // We need languages list on all pages
      // We prepare it here to be able to choose enabled languages from here
      const languages = []
      _.each(
        languagesRes.data.allContentfulWebsite.edges,
        ({node: {languageName, languagePath, node_locale}}) => {
          languagePaths[node_locale] = languagePath
          languages.push({
            languageName,
            languagePath,
          })
        },
      )

      // layout for each lang
      Object.keys(languagePaths).forEach((locale) => {
        createLayout({
          component: slash(MainLayout),
          id: `main-layout-${locale}`,
          context: {
            languages,
            locale,
          },
        })
      })

      // home pages
      Object.entries(languagePaths).forEach(([locale, languagePath]) => {
        createPage({
          path: languagePath,
          component: slash(homeTemplate),
          context: {
            languagePath,
            languages,
            locale,
          },
          layout: `main-layout-${locale}`,
        })
      })
      resolve()
    })
  })
}

MainLayout.jsx

import * as React from 'react'
import 'tachyons' // eslint-disable-line
import LanguagesNav from '../components/LanguagesNav'
import './styles.css'

const MainLayout = ({children, data, layoutContext, location}) => {
  const {languageChoiceLabel} = data.contentfulWebsite
  const {languages} = layoutContext
  const {pathname} = location
  return (
    <div>
      <LanguagesNav
        languageChoiceLabel={languageChoiceLabel}
        languages={languages}
        pathname={pathname}
      />
      {children()}
    </div>
  )
}

export default MainLayout

export const pageQuery = graphql`
  query mainWrapperQuery($locale: String!) {
    contentfulWebsite(node_locale: {eq: $locale}) {
      languageChoiceLabel
    }
  }
`

For the translated slugs I don't know how you have to do it though.

@danjebs
Copy link

danjebs commented Jan 17, 2018

Thanks @abumalick, I ended up using the English (base) slugs for the translated articles.

Seems it's the way Gatsby is structured. In Jekyll you don't specifically pass variables back up the layout, but the layout will execute one time for each page and so it can expect variables as if it were the page itself. In Gatsby it seems to render the template once, then repeat that for each page.

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

Successfully merging a pull request may close this issue.

3 participants