Gatsby: Action createPage was called outside of its expected asynchronous lifecycle

Created on 13 Nov 2019  路  2Comments  路  Source: gatsbyjs/gatsby

Summary

Relevant information

I am trying to fetch external data and pass it to the page, but no matter what I try to do I always receive this error. Please help me with some advice, thanks.

File contents (if changed)

instagram.js:

const axios = require('axios')
const queryString = require('query-string')

exports.getData = url => {
  const params = queryString.stringify({
    url,
    maxwidth: 700,
    hidecaption: true,
    omitscript: true
  })

  return axios.get(`https://api.instagram.com/oembed/?${params}`)
    .then(res => {
      const {data} = res
      return {
        imageUrl: data.thumbnail_url || false,
        imageWidth: data.thumbnail_width || false,
        imageHeight: data.thumbnail_height || false,
        description: data.title || ''
      }
    })
    .catch(err => {
      console.log(err)
      return false
    })
}

gatsby-node.js:

const path = require(`path`)
const config = require('./src/utils/siteConfig')
const instagram = require('./src/utils/instagram')

exports.createPages = async ({ graphql, actions }) => {
  const { createPage } = actions

  const loadProjects = new Promise((resolve, reject) => {
    graphql(`
      {
        allContentfulProject(limit: 10000) {
          edges {
            node {
              slug
              sourceUrl
            }
          }
        }
      }
    `).then(result => {
      const posts = result.data.allContentfulProject.edges
      const postsPerFirstPage = config.postsPerHomePage
      const postsPerPage = config.postsPerPage
      const numPages =
        Math.ceil(posts.slice(postsPerFirstPage).length / postsPerPage) + 1

      // Create each individual project
      posts.forEach(async (edge, i) => {
        const prev = i === 0 ? null : posts[i - 1].node
        const next = i === posts.length - 1 ? null : posts[i + 1].node

        const postData = await instagram
          .getData(edge.node.sourceUrl)
          .catch(err => {
            console.log(err)
            console.log(`=PROJECTS= : Can't fetch ${edge.node.sourceUrl}`)
          })

        // console.log(postData)
        if (postData) {
          createPage({
            path: `/projects/${edge.node.slug}/`,
            component: path.resolve(`./src/templates/project.jsx`),
            context: {
              slug: edge.node.slug,
              postData,
              prev,
              next
            }
          })
        }

      })
      resolve()
    })
  })

  await Promise.all([loadProjects])
}

exports.onCreateWebpackConfig = ({ getConfig, actions }) => {
  if (getConfig().mode === 'production') {
    actions.setWebpackConfig({
      devtool: false
    })
  }
}

Most helpful comment

The issue you're having are because you're not awaiting posts.forEach(async (edge, i) => {

This should do the trick

gatsby-node.js:

const path = require(`path`)
const config = require('./src/utils/siteConfig')
const instagram = require('./src/utils/instagram')

exports.createPages = async ({ graphql, actions }) => {
  const { createPage } = actions

  const loadProjects = new Promise((resolve, reject) => {
    graphql(`
      {
        allContentfulProject(limit: 10000) {
          edges {
            node {
              slug
              sourceUrl
            }
          }
        }
      }
    `).then(result => {
      const posts = result.data.allContentfulProject.edges
      const postsPerFirstPage = config.postsPerHomePage
      const postsPerPage = config.postsPerPage
      const numPages =
        Math.ceil(posts.slice(postsPerFirstPage).length / postsPerPage) + 1

      // Create each individual project
      Promise.all(posts.map(async (edge, i) => {
        const prev = i === 0 ? null : posts[i - 1].node
        const next = i === posts.length - 1 ? null : posts[i + 1].node

        const postData = await instagram
          .getData(edge.node.sourceUrl)
          .catch(err => {
            console.log(err)
            console.log(`=PROJECTS= : Can't fetch ${edge.node.sourceUrl}`)
          })

        // console.log(postData)
        if (postData) {
          createPage({
            path: `/projects/${edge.node.slug}/`,
            component: path.resolve(`./src/templates/project.jsx`),
            context: {
              slug: edge.node.slug,
              postData,
              prev,
              next
            }
          })
        }

      })).then(resolve)
    })
  })

  await Promise.all([loadProjects])
}

If you want a more async-await style you can use this

gatsby-node.js:

const path = require(`path`)
const config = require('./src/utils/siteConfig')
const instagram = require('./src/utils/instagram')

exports.createPages = async ({ graphql, actions }) => {
  const { createPage } = actions

  const results = graphql(`
    {
      allContentfulProject(limit: 10000) {
        edges {
          node {
            slug
            sourceUrl
          }
        }
      }
    }
  `)

  const posts = result.data.allContentfulProject.edges
  const postsPerFirstPage = config.postsPerHomePage
  const postsPerPage = config.postsPerPage
  const numPages =
    Math.ceil(posts.slice(postsPerFirstPage).length / postsPerPage) + 1

  // Create each individual project
  const promises = posts.map(async (edge, i) => {
    const prev = i === 0 ? null : posts[i - 1].node
    const next = i === posts.length - 1 ? null : posts[i + 1].node

    let postData;
    try {
      postData = await instagram.getData(edge.node.sourceUrl)
    } catch(err) {
      console.log(err)
      console.log(`=PROJECTS= : Can't fetch ${edge.node.sourceUrl}`)
    }

    // console.log(postData)
    if (postData) {
      createPage({
        path: `/projects/${edge.node.slug}/`,
        component: path.resolve(`./src/templates/project.jsx`),
        context: {
          slug: edge.node.slug,
          postData,
          prev,
          next
        }
      })
    }
  }

  await Promise.all(promises)
}

We're marking this issue as answered and closing it for now but please feel free to reopen this and comment if you would like to continue this discussion. We hope we managed to help and thank you for using Gatsby! 馃挏

All 2 comments

The issue you're having are because you're not awaiting posts.forEach(async (edge, i) => {

This should do the trick

gatsby-node.js:

const path = require(`path`)
const config = require('./src/utils/siteConfig')
const instagram = require('./src/utils/instagram')

exports.createPages = async ({ graphql, actions }) => {
  const { createPage } = actions

  const loadProjects = new Promise((resolve, reject) => {
    graphql(`
      {
        allContentfulProject(limit: 10000) {
          edges {
            node {
              slug
              sourceUrl
            }
          }
        }
      }
    `).then(result => {
      const posts = result.data.allContentfulProject.edges
      const postsPerFirstPage = config.postsPerHomePage
      const postsPerPage = config.postsPerPage
      const numPages =
        Math.ceil(posts.slice(postsPerFirstPage).length / postsPerPage) + 1

      // Create each individual project
      Promise.all(posts.map(async (edge, i) => {
        const prev = i === 0 ? null : posts[i - 1].node
        const next = i === posts.length - 1 ? null : posts[i + 1].node

        const postData = await instagram
          .getData(edge.node.sourceUrl)
          .catch(err => {
            console.log(err)
            console.log(`=PROJECTS= : Can't fetch ${edge.node.sourceUrl}`)
          })

        // console.log(postData)
        if (postData) {
          createPage({
            path: `/projects/${edge.node.slug}/`,
            component: path.resolve(`./src/templates/project.jsx`),
            context: {
              slug: edge.node.slug,
              postData,
              prev,
              next
            }
          })
        }

      })).then(resolve)
    })
  })

  await Promise.all([loadProjects])
}

If you want a more async-await style you can use this

gatsby-node.js:

const path = require(`path`)
const config = require('./src/utils/siteConfig')
const instagram = require('./src/utils/instagram')

exports.createPages = async ({ graphql, actions }) => {
  const { createPage } = actions

  const results = graphql(`
    {
      allContentfulProject(limit: 10000) {
        edges {
          node {
            slug
            sourceUrl
          }
        }
      }
    }
  `)

  const posts = result.data.allContentfulProject.edges
  const postsPerFirstPage = config.postsPerHomePage
  const postsPerPage = config.postsPerPage
  const numPages =
    Math.ceil(posts.slice(postsPerFirstPage).length / postsPerPage) + 1

  // Create each individual project
  const promises = posts.map(async (edge, i) => {
    const prev = i === 0 ? null : posts[i - 1].node
    const next = i === posts.length - 1 ? null : posts[i + 1].node

    let postData;
    try {
      postData = await instagram.getData(edge.node.sourceUrl)
    } catch(err) {
      console.log(err)
      console.log(`=PROJECTS= : Can't fetch ${edge.node.sourceUrl}`)
    }

    // console.log(postData)
    if (postData) {
      createPage({
        path: `/projects/${edge.node.slug}/`,
        component: path.resolve(`./src/templates/project.jsx`),
        context: {
          slug: edge.node.slug,
          postData,
          prev,
          next
        }
      })
    }
  }

  await Promise.all(promises)
}

We're marking this issue as answered and closing it for now but please feel free to reopen this and comment if you would like to continue this discussion. We hope we managed to help and thank you for using Gatsby! 馃挏

@wardpeet Just a small addition....
For your async example to work you also can't forget to await on the results.

const results = await graphql(`
    {
      allContentfulProject(limit: 10000) {
        edges {
          node {
            slug
            sourceUrl
          }
        }
      }
    }
  `)
Was this page helpful?
0 / 5 - 0 ratings

Related issues

ferMartz picture ferMartz  路  3Comments

3CordGuy picture 3CordGuy  路  3Comments

Oppenheimer1 picture Oppenheimer1  路  3Comments

dustinhorton picture dustinhorton  路  3Comments

andykais picture andykais  路  3Comments