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

jimfilippou picture jimfilippou  路  3Comments

timbrandin picture timbrandin  路  3Comments

hobochild picture hobochild  路  3Comments

ghost picture ghost  路  3Comments

ferMartz picture ferMartz  路  3Comments