'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 1:[1]
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.
Solution 2:[2]
I had the same problem, the solution I found was to use createResolvers
.
exports.createResolvers = ({ createResolvers }) => {
const resolvers = {
HomePageValuesBannerImage: {
bannerImageFile: {
type: "File",
resolve: async (source, args, context, info) => {
const homePage = await context.nodeModel.findRootNodeAncestor(source)
const imageId = homePage.fields.bannerImage
const imageNode = await context.nodeModel.getNodeById({
id: imageId,
})
return imageNode
},
},
},
}
createResolvers(resolvers)
}
So the first thing I needed was to access the fields
field on my parent object, because whenever you use createNodeField
it creates a field with the name you provide inside the node on fields
.
To access the node file id that is stored on fields
, in my case is called bannerImage
on your example nested_image
, you first need a reference of the parent node so in order to do that I use context.nodeModel.findRootNodeAncestor(source)
source is just a reference of the nested property. In my case is HomePageValuesBannerImage
because my data looks like this:
HomePage {
Values {
banner {
image
}
}
}
and in my schema the banner property's type is HomePageValuesBannerImage.
Then you need to create a new field, on the documentation says that is better to create a new field than updating one.
The field I created is called bannerImageFile
and inside that field is going to be the File Node.
After making the query for the parent of my nested node I just extract the file id that is stored inside fields
in my custom field called bannerImage
.
And finally I made a query to find a node by its ID and return it.
const imageNode = await context.nodeModel.getNodeById({
id: imageId,
})
Im still looking for a different approach to this problem, but this is the best solutions I could find.
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
Solution | Source |
---|---|
Solution 1 | Jeremy Caney |
Solution 2 | José Luna |