MostXlent1
MostXlent1

Reputation: 123

Gatsby createPages lifecycle method running before onCreateNode method finishes adding needed node fields

I am working on a Gatsby site that uses gatsby-source-wordpress. In my gatbsy-node.js file, I use the onCreateNote lifecycle method to determine if the node is a certain WordPress custom post type, then I reach out to a separate API to get related information for the post type, use createNodeField to add it as a field, and sometimes also use createRemoteFileNode to add images sourced from the API to a field on the new node.

Now this works great most of the time, but occasionally the createPages lifecycle method runs while the image/node code is still happening (I believe). This means that the image fields don't exist yet, and the page creation fails. Then after it fails, I see a console message in the log that I set up where it notifies me that the new field has successfully been added to the node.

How can I make sure that all of those nodes are finished and the data is complete, BEFORE the createPages lifecycle runs? It seems when the client uploads a larger image, this is more likely to fail... which makes sense if I'm understanding this correctly. Here is the code from my gatsby-node.js file:

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

const fetch = require('node-fetch');
const { createRemoteFileNode } = require(`gatsby-source-filesystem`)


exports.onCreateNode = ({ node, actions, store, cache,createNodeId, }) => {
    const { createNode, createNodeField } = actions;

    function getData(url) {
        return new Promise((resolve, reject) => {
            fetch(url)
                .then((response) => response.json())
                .then((data) => {
                    resolve(data);
                });
        })
    }

    if( node.internal.type === "wordpress__wp_location"){
        const yextID = node.acf.yext_entity_id;
        const yextOrthos = node.acf.location_orthodontists;

        try {

            const getLocation = async () => {
                const data = await fetch("https://api.yext.com/v2/accounts/me/entities?api_key=" + process.env.YEXT_API_KEY + "&v=20191114&filter=%7B%22%24anywhere%22%3A%20%22" + yextID + "%22%7D&entityTypes=healthcareFacility")
                .then(response => response.json());

                // Transform the data into json
                if( data && data.response && data.response.count === 1 ){
                    createNodeField({
                        node,
                        name: `yextLocation`,
                        value: data.response.entities[0]
                    });
                } else {
                    console.log("NO LOCATIONS FOUND");
                }
            };

            function getOrthos(){
                let orthodontists = [];

                yextOrthos.forEach( (ortho, i) => {
                    orthodontists.push(getData("https://api.yext.com/v2/accounts/me/entities?api_key=" + process.env.YEXT_API_KEY + "&v=20191114&filter=%7B%22%24anywhere%22%3A%20%22" + ortho.acf.yext_entity_ortho_id + "%22%7D&entityTypes=healthcareProfessional"));
                });

                Promise.all(orthodontists).then( (orthoData) => {
                    if( orthoData.length ){
                        let finalOrthos = [];

                        orthoData.forEach( (finalOrtho, x) => {
                            finalOrthos.push(finalOrtho.response.entities[0]);
                        });

                        createNodeField({
                            node,
                            name: `yextOrthos`,
                            value: finalOrthos
                        });

                    } else {
                        console.log("NO DOCTORS FOUND");
                    }
                });
            }

            getLocation();
            getOrthos();

        } catch (error) {
            console.log(error);
        }
    }

    if( node.internal.type === "wordpress__wp_orthodontist"){
        const yextID = node.acf.yext_entity_ortho_id;
        const wpID = node.wordpress_id;

        try {

            const getTextOrtho = async () => {
                const data = await fetch("https://api.yext.com/v2/accounts/me/entities?api_key=" + process.env.YEXT_API_KEY + "&v=20191114&filter=%7B%22%24anywhere%22%3A%20%22" + yextID + "%22%7D&entityTypes=healthcareProfessional")
                .then(response => response.json());

                // Transform the data into json
                if( data && data.response && data.response.count === 1 ){
                    googleProfilePhoto = data.response.entities[0].googleProfilePhoto.url;
                    createNodeField({
                        node,
                        name: `yextOrthodontist`,
                        value: data.response.entities[0]
                    });

                    if( data.response.entities[0].googleProfilePhoto && data.response.entities[0].googleProfilePhoto.url){
                        createNodeField({
                            node,
                            name: `yextProfilePicture`,
                            value: data.response.entities[0].googleProfilePhoto.url
                        });

                        let fileNode = await createRemoteFileNode({
                            url: data.response.entities[0].googleProfilePhoto.url, // string that points to the URL of the image
                            parentNodeId: node.id, // id of the parent node of the fileNode you are going to create
                            createNode, // helper function in gatsby-node to generate the node
                            createNodeId, // helper function in gatsby-node to generate the node id
                            cache, // Gatsby's cache
                            store, // Gatsby's redux store
                        })
                        // if the file was created, attach the new node to the parent node
                        if (fileNode) {
                            console.log("GOOGLE PROFILE NODE CREATED!")
                            node.featuredImg___NODE = fileNode.id
                        } else {
                            console.log("ERROR! fileNode not Created!");
                        }
                    } else {
                        console.log("NO GOOGLE PROFILE PHOTO FOUND");
                    }
                    
                } else {
                    console.log("NO ORTHODONTISTS FOUND");
                }
            }

            const getWpLocations = async () => {
                const data = await fetch(process.env.GATSBY_WP_BASEURL+ "/wp-json/custom_endpoint/v1/locations_by_orthodontist?orthodontist_id=" + wpID).then(response => response.json());

                if( data ){
                    createNodeField({
                        node,
                        name: `wpLocations`,
                        value: data
                    });
                } else {
                    console.log("NO ORTHODONTISTS FOUND");
                }
            }

            getTextOrtho();
            getWpLocations();

        } catch (error) {
            console.log(error);
        }
    }

}

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

    const result = await graphql(`
        {
            locations: allWordpressWpLocation(filter: {status: {eq: "publish"}}) {
                nodes {
                    id
                    path
                    acf {
                        location_orthodontists {
                            acf {
                                yext_entity_ortho_id
                            }
                        }
                        yext_entity_id
                    }
                }
            }
            pages: allWordpressPage(
                filter: {
                    wordpress_id: {nin: [177, 183, 8, 42, 44, 185, 46]}
                    status: {eq: "publish"}
                }) {
                nodes {
                    id
                    wordpress_id
                    path
                }
            }
            orthodontists: allWordpressWpOrthodontist(filter: {status: {eq: "publish"}}) {
                nodes {
                    id
                    path
                }
            }
            posts: allWordpressPost(filter: {status: {eq: "publish"}}) {
                nodes {
                    slug
                    id
                }
            }
        }
    `);

    // Check for any errors
    if (result.errors) {
        throw new Error(result.errors);
    }

    const { locations, pages, orthodontists, posts } = result.data;

    const locationTemplate = path.resolve(`./src/templates/location.js`);
    const pageTemplate = path.resolve(`./src/templates/page.js`);
    const orthoTemplate = path.resolve(`./src/templates/orthodontist.js`);
    const postTemplate = path.resolve(`./src/templates/post.js`);
    const blogTemplate = path.resolve(`./src/templates/blog.js`);

    locations.nodes.forEach(node => {
        let orthodontists = [];

        node.acf.location_orthodontists.forEach(ortho => {
            orthodontists.push(ortho.acf.yext_entity_ortho_id);
        });

        let orthodontistList = orthodontists.join();

        createPage({
            path: `${node.path}`,
            component: slash(locationTemplate),
            context: {
                id: node.id,
                yextId: node.acf.yext_entity_id,
                yextOrthoIds: orthodontists
            },
        });
    });

    pages.nodes.forEach(node => {
        createPage({
            path: `${node.path}`,
            component: slash(pageTemplate),
            context: {
                id: node.id,
            },
        });
    });

    orthodontists.nodes.forEach(node => {
        createPage({
            path: `${node.path}`,
            component: slash(orthoTemplate),
            context: {
                id: node.id,
            },
        });
    });

    posts.nodes.forEach(node => {
        createPage({
            path: `${node.slug}`,
            component: slash(postTemplate),
            context: {
                id: node.id,
            },
        });
    });

    const postsPerPage = 12;
    const numPages = Math.ceil(posts.nodes.length / postsPerPage);
    Array.from({ length: numPages }).forEach((_, i) => {
        createPage({
            path: i === 0 ? `/blog` : `/blog/page/${i + 1}`,
            component: slash(blogTemplate),
            context: {
                limit: postsPerPage,
                skip: i * postsPerPage,
                numPages,
                currentPage: i + 1,
            },
        })
    })
};

Thanks for any information you can provide! I imagine this is probably due to me still learning to use asynchronous behavior in JS, but I just can't seem to find information on how to make this happen.

Please let me know if I can explain the situation any better!

Upvotes: 0

Views: 870

Answers (1)

MostXlent1
MostXlent1

Reputation: 123

After a rewrite, this seems to have solved the issue I was having. I'll be honest, I'm still working on completely understanding the ins and outs on async/await/promises functionality in JS, but hopefully if someone encounters a similar problem, viewing this rewrite may help:

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

const fetch = require('node-fetch');
const { createRemoteFileNode } = require(`gatsby-source-filesystem`)


exports.onCreateNode = async ({ node, actions, store, cache,createNodeId, }) => {
    const { createNode, createNodeField } = actions;

    const getData = async (url) => {
        return new Promise((resolve, reject) => {
            fetch(url)
                .then((response) => response.json())
                .then((data) => {
                    resolve(data);
                });
        })
    }

    const getLocation = async (yextID) => {
        const data = await getData("https://api.yext.com/v2/accounts/me/entities?api_key=" + process.env.YEXT_API_KEY + "&v=20191114&filter=%7B%22%24anywhere%22%3A%20%22" + yextID + "%22%7D&entityTypes=healthcareFacility");

        // Transform the data into json
        if( data && data.response && data.response.count === 1 ){
            createNodeField({
                node,
                name: `yextLocation`,
                value: data.response.entities[0]
            });
        } else {
            console.log("NO LOCATIONS FOUND");
        }
    };

    const getOrthos = async (yextOrthos) => {
        let orthodontists = [];

        yextOrthos.forEach( (ortho, i) => {
            orthodontists.push(getData("https://api.yext.com/v2/accounts/me/entities?api_key=" + process.env.YEXT_API_KEY + "&v=20191114&filter=%7B%22%24anywhere%22%3A%20%22" + ortho.acf.yext_entity_ortho_id + "%22%7D&entityTypes=healthcareProfessional"));
        });

        Promise.all(orthodontists).then( (orthoData) => {
            if( orthoData.length ){
                let finalOrthos = [];

                orthoData.forEach( (finalOrtho, x) => {
                    finalOrthos.push(finalOrtho.response.entities[0]);
                });

                createNodeField({
                    node,
                    name: `yextOrthos`,
                    value: finalOrthos
                });

            } else {
                console.log("NO DOCTORS FOUND");
            }
        });
    };

    const getTextOrtho = async (yextID) => {
        const data = await getData("https://api.yext.com/v2/accounts/me/entities?api_key=" + process.env.YEXT_API_KEY + "&v=20191114&filter=%7B%22%24anywhere%22%3A%20%22" + yextID + "%22%7D&entityTypes=healthcareProfessional");

        if( data && data.response && data.response.count === 1 ){
            createNodeField({
                node,
                name: `yextOrthodontist`,
                value: data.response.entities[0]
            });

            if( data.response.entities[0].googleProfilePhoto && data.response.entities[0].googleProfilePhoto.url){
                createNodeField({
                    node,
                    name: `yextProfilePicture`,
                    value: data.response.entities[0].googleProfilePhoto.url
                });

                let fileNode = await createRemoteFileNode({
                    url: data.response.entities[0].googleProfilePhoto.url, // string that points to the URL of the image
                    parentNodeId: node.id, // id of the parent node of the fileNode you are going to create
                    createNode, // helper function in gatsby-node to generate the node
                    createNodeId, // helper function in gatsby-node to generate the node id
                    cache, // Gatsby's cache
                    store, // Gatsby's redux store
                });
                // if the file was created, attach the new node to the parent node
                if (fileNode) {
                    node.featuredImg___NODE = fileNode.id;
                    console.log("GOOGLE PROFILE NODE CREATED!")
                } else {
                    console.log("ERROR! fileNode not Created!");
                }
            } else {
                console.log("NO GOOGLE PROFILE PHOTO FOUND");
            }
            
        } else {
            console.log("NO ORTHODONTISTS FOUND");
        }
    };

    const getWpLocations = async (wpID) => {
        const data = await getData(process.env.GATSBY_WP_BASEURL+ "/wp-json/perch_endpoint/v1/locations_by_orthodontist?orthodontist_id=" + wpID);

        if( data ){
            createNodeField({
                node,
                name: `wpLocations`,
                value: data
            });
        } else {
            console.log("NO ORTHODONTISTS FOUND");
        }
    }

    if( node.internal.type === "wordpress__wp_location"){
        const yextID = node.acf.yext_entity_id;
        const yextOrthos = node.acf.location_orthodontists;

        try {
            await getLocation(yextID);
            await getOrthos(yextOrthos);
        } catch (error) {
            console.log(error);
        }

    }

    if( node.internal.type === "wordpress__wp_orthodontist"){
        const yextID = node.acf.yext_entity_ortho_id;
        const wpID = node.wordpress_id;

        try {
            await getTextOrtho(yextID);
            await getWpLocations(wpID);
        } catch (error) {
            console.log(error);
        }
    }

}

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

    const result = await graphql(`
        {
            locations: allWordpressWpLocation(filter: {status: {eq: "publish"}}) {
                nodes {
                    id
                    path
                    acf {
                        location_orthodontists {
                            acf {
                                yext_entity_ortho_id
                            }
                        }
                        yext_entity_id
                    }
                }
            }
            pages: allWordpressPage(
                filter: {
                    wordpress_id: {nin: [177, 183, 8, 42, 44, 185, 46]}
                    status: {eq: "publish"}
                }) {
                nodes {
                    id
                    wordpress_id
                    path
                }
            }
            orthodontists: allWordpressWpOrthodontist(filter: {status: {eq: "publish"}}) {
                nodes {
                    id
                    path
                }
            }
            posts: allWordpressPost(filter: {status: {eq: "publish"}}) {
                nodes {
                    slug
                    id
                }
            }
        }
    `);

    // Check for any errors
    if (result.errors) {
        throw new Error(result.errors);
    }

    const { locations, pages, orthodontists, posts } = result.data;

    const locationTemplate = path.resolve(`./src/templates/location.js`);
    const pageTemplate = path.resolve(`./src/templates/page.js`);
    const orthoTemplate = path.resolve(`./src/templates/orthodontist.js`);
    const postTemplate = path.resolve(`./src/templates/post.js`);
    const blogTemplate = path.resolve(`./src/templates/blog.js`);

    locations.nodes.forEach(node => {
        let orthodontists = [];

        node.acf.location_orthodontists.forEach(ortho => {
            orthodontists.push(ortho.acf.yext_entity_ortho_id);
        });

        let orthodontistList = orthodontists.join();

        createPage({
            path: `${node.path}`,
            component: slash(locationTemplate),
            context: {
                id: node.id,
                yextId: node.acf.yext_entity_id,
                yextOrthoIds: orthodontists
            },
        });
    });

    pages.nodes.forEach(node => {
        createPage({
            path: `${node.path}`,
            component: slash(pageTemplate),
            context: {
                id: node.id,
            },
        });
    });

    orthodontists.nodes.forEach(node => {
        createPage({
            path: `${node.path}`,
            component: slash(orthoTemplate),
            context: {
                id: node.id,
            },
        });
    });

    posts.nodes.forEach(node => {
        createPage({
            path: `${node.slug}`,
            component: slash(postTemplate),
            context: {
                id: node.id,
            },
        });
    });

    const postsPerPage = 12;
    const numPages = Math.ceil(posts.nodes.length / postsPerPage);
    Array.from({ length: numPages }).forEach((_, i) => {
        createPage({
            path: i === 0 ? `/blog` : `/blog/page/${i + 1}`,
            component: slash(blogTemplate),
            context: {
                limit: postsPerPage,
                skip: i * postsPerPage,
                numPages,
                currentPage: i + 1,
            },
        })
    })
};

Upvotes: 3

Related Questions