Gatsby Wordpress ACF Flexible Content
Tags: gatsby, wordpress, acf, graphql
Published: 2021-02-14 06:46:39

Let me tell you a story about some poor sod (definitely not me) who is excited about gatsby + wordpress but is lead astray by dark forces out of his control.

Let's say you want to create a page template where pages can be made out of components defined in your flexible content field. You are trying to get the data for all these components in `src/templates/page.js`:
```js
export const query = graphql`
  query PageQuery($id: String!) {
    wpPage(id: {eq: $id}) {
      title
      page_components {
        components {
          __typename
          ... on WpPage_PageComponents_Components_BannerOne {
            title
            button {
              text
              link
            }
            # More stuff
          }
          ... on WpPage_PageComponents_Components_BannerTwo {
            title
            image
            # More stuff
          }
        }
      }
    }
  }
`
```
It would be against the Single-responsibility principle in the SOLID principles to have the component queries as part of the page query, so you turn these component queries into fragments:

For example, in `src/components/page/BannerOne/BannerOne.js`:
```js
export const query = graphql`
  fragment BannerOneFragment on WpPage_PageComponents_Components_BannerOne {
    title
    button {
      text
      link
    }
    # More stuff
  }
`
```

And now your page query in `src/templates/page.js` looks like:
```js
export const query = graphql`
  query PageQuery($id: String!) {
    wpPage(id: {eq: $id}) {
      title
      page_components {
        components {
          __typename
          ... on WpPage_PageComponents_Components_BannerOne {
            ...BannerOneFragment
          }
          ... on WpPage_PageComponents_Components_BannerTwo {
            ...BannerTwoFragment
          }
        }
      }
    }
  }
`
```

Great! Things are looking better already. However we still haven't completely separated concerns, since when we add a new component, we have to remember to add its fragment to the page query. Not the end of the world, but not ideal either.

You notice the graphql query is a template literal, so maybe you can dynamically generate the components query? Assuming you could get the `componentNames` by reading all the component file names from your file system, it would look something like:

```js
export const query = graphql`
  query PageQuery($id: String!) {
    wpPage(id: {eq: $id}) {
      title
      page_components {
        components {
          __typename
          ${componentNames.map(componentName => {
            return `
              ... on WpPage_PageComponents_Components_${componentName} {
                ...${componentName}Fragment
              }
            `
          }).join()}
        }
      }
    }
  }
`
```

Ah! An error! What you are trying to do is extremely illegal! So you put up with adding new fragments to the page query every time you add a new component. But then you decide: wouldn't it be great to build other post types out of components too?

Now it starts to get messy. Not only do you have to duplicate the fragments in the page query (`src/templates/page.js`):

```js
export const query = graphql`
  query PageQuery($id: String!) {
    wpPage(id: {eq: $id}) {
      title
      page_components {
        components {
          __typename
          ... on WpPage_PageComponents_Components_BannerOne {
            ...BannerOnePageFragment
          }
          ... on WpPage_PageComponents_Components_BannerTwo {
            ...BannerTwoPageFragment
          }
          ... on WpProject_PageComponents_Components_BannerOne {
            ...BannerOneProjectFragment
          }
          ... on WpProject_PageComponents_Components_BannerTwo {
            ...BannerTwoProjectFragment
          }
        }
      }
    }
  }
`
```

You also have to duplicate the fragments in the components too (`src/components/page/BannerOne/BannerOne.js`):
```js
export const query = graphql`
  fragment BannerOnePageFragment on WpPage_PageComponents_Components_BannerOne {
    title
    button {
      text
      link
    }
    # More stuff
  }
  fragment BannerOneProjectFragment on WpProject_PageComponents_Components_BannerOne {
    title
    button {
      text
      link
    }
    # More stuff
  }
`
```

And you can imagine the pain when trying to add more post types...

## Several weeks later

Then God Himself (and by God I mean [Henrik Wirth](https://dev.to/nevernull/pagebuilder-with-acf-flexible-content-guide-to-gatsby-wordpress-starter-advanced-with-previews-i18n-and-more-2lf4)) comes to you in a dream and tells you that all these problems can be fixed by generating the template file (`src/templates/page.js`) yourself before Gatsby gets its filthy hands on it!

This way you can dynamically generate the page query like we wanted to earlier. We can also create separate template files for each of the different post types. Fantastic! It would look something like this in `gatsby-node.js`:

```js
postTypes.forEach(postType => {
  createTemplate(postType)
})
pages.forEach(page => {
    const template = getTemplate(page.postType)
  gatsbyUtilities.actions.createPage({
    path: page.uri,
    component: template,
    context: {
      id: page.id,
    },
  })
})

```

The createTemplate function would create a template file for every post type, e.g. `.cache/page-templates/page.js` and `.cache/page-templates/project.js`, and the queries in each of those files would be automatically populated with all the component fragments.

But wait, we can optimize even further!

Right now, every template file imports every component that exists, which bloats pages that are not using every single component. What if we generated template files for every single page, rather than just for every post type?

This way we import only the components that a page needs.

If you'd like to see how this works in practice, please visit
[gatsby-wp-acf-starter](https://github.com/elliott-w/gatsby-wp-acf-starter) for a rigorously commented starter project.