Search code examples
gatsby

Create nested file nodes Gatsby 4


Since Gatsby 4 doesn't accept remoteFileNodes to be created within the createResolvers API inside gatsby-node.js, i'm looking for another solution to create File's from our remote (graphql) source.

Creating files on the upper level of an object works just fine, however i can't find a way to create these files inside nested objects in my schema.

Although there is a File object created with the provided name inside Sections, all the data inside of it results in null. The given URL is checked and is valid.

The following code is inside my gatsby-node.js file:

sourceNodes

exports.sourceNodes = async ({ actions, createContentDigest, createNodeId }) => {
  const { createNode } = actions;

  const { data } = await client.query({
    query: gql`
      query {
        
        PageContent {
          id
          main_image_url
          blocks {
            title
            sections {
              title
              nested_image_url
            }
          }
        }
      }
    `,
  });

  data.PageContent.forEach((pageContent) => {
    createNode({
      ...pageContent,
      id: createNodeId(`${PAGE_CONTENT}-${pageContent.id}`),
      parent: null,
      children: [],
      internal: {
        type: PAGE_CONTENT,
        content: JSON.stringify(pageContent),
        contentDigest: createContentDigest(pageContent),
      }
    })
  });

  return;
};

onCreateNode

exports.onCreateNode = async ({
  node,
  actions: { createNode, createNodeField },
  createNodeId,
  getCache,
}) => {

  if (node.internal.type === PAGE_CONTENT) {

This works just fine

    if (node.main_image_url) {
      const fileNode = await createRemoteFileNode({
        url: node.main_image_url,
        parentNodeId: node.id,
        createNode,
        createNodeId,
        getCache,
      });
  
      if (fileNode) {
        createNodeField({ node, name: "main_image", value: fileNode.id });
      }
    }

But this won't

    if (node.blocks && node.blocks.length > 0) {
      node.blocks.map(async({ sections }) => {

        if (sections.length > 0) {

          sections.map(async(section) => {

            if (section.nested_image_url) {

              const fileNode = await createRemoteFileNode({
                url: section.nested_image_url,
                parentNodeId: node.id,
                createNode,
                createNodeId,
                getCache,
              });
  
              if (fileNode) {
                createNodeField({ node, name: "nested_image", value: fileNode.id });
              }
            }
          })
        }
      })
    }
  }
};

createSchema

exports.createSchemaCustomization = ({ actions }) => {
  const { createTypes } = actions;

  createTypes(`
    type PageContent implements Node {
      main_image: File @link(from: "fields.main_image")
      blocks: [Block]
    }

    type Block {
      sections: [Section]
    }

    type Section {
      nested_image: File @link(from: "fields.nested_image")
    }
  `);
};

Would be really grateful if someone has a clue!


Solution

  • Meanwhile I've come to a solution, which includes the use of the onCreateNode and createSchemaCustomization API's

    onCreateNode

      // Single Node
    
      const fileNode = await createRemoteFileNode({
         url: node.image,
         parentNodeId: node.id,
         createNode,
         createNodeId: (id) => `${node.unique_identifier_prop}-image`,
         getCache,
       });
    
        // Array
    
        await Promise.all(
          node.images.map((url, index) => (
            createRemoteFileNode({
              url: url,
              parentNodeId: node.id,
              createNode,
              createNodeId: id => `${node.unique_identifier_prop}-images-${index}`,
              getCache,
            })
          ))
        )
    

    First you can create a FileNode to your own liking, and instead of the API's createNodeId function. We replace it by a unique and retrievable identifier, so we can locate the File Node in our Schema.

    createSchemaCustomization

    exports.createSchemaCustomization = async({ actions, schema }) => {
      const { createTypes } = actions;
    
      const typeDefs = [
        schema.buildObjectType({
          name: `**target_typename**`,
          fields: {
            // Single Node
            imageFile: {
              type: 'File',
              resolve: (source, args, context, info) => {
                return context.nodeModel.getNodeById({
                  id: `${source.unique_identifier_prop}-image`,
                  type: 'File',
                })
              }
            },
            // Array
            imageFiles: {
              type: '[File]',
              resolve: (source, args, context, info) => {
                const images = source.images.map((img, index) => (
                  context.nodeModel.getNodeById({
                    id: `${source.unique_identifier_prop}-images-${index}`,
                    type: 'File',
                  })
                ))
                return images
              }
            },
          }
        })
      ];
    
      createTypes(typeDefs)
    };
    

    In the createSchemaCustomization we now can define our custom type with the buildObjectType function provided by schema, which is available in this API.

    In the resolver, we can retrieve the node's values with the source parameter, which holds our unique_identifier_prop. Now, with the context parameter, we can use the getNodeById function to retrieve the File Node that is bound to our provided ID. Finally, we can return the found File Node and attach it to our Node.