In this section, we’ll examine the schema inference Gatsby undertakes to define child fields that have a relationship to their parent field. Consider the example of the File
type, for which many transformer plugins exist that convert a file’s contents into a format legible to Gatsby’s data layer. When transformer plugins implement onCreateNode
for each File
node, this implementation produces File
child nodes that carry their own type (e.g., markdownRemark
or postsJson
).
When Gatsby infers the schema for these child fields, it stores the nodes in Redux by identifying them through id
s in each parent’s children
field. Then, Gatsby stores those child nodes in Redux as full nodes in their own right. For instance, a File
node having two children will be stored in the Redux nodes
namespace as follows:
{
`id1`: { type: `File`, children: [`id2`, `id3`], ...other_fields },
`id2`: { type: `markdownRemark`, ...other_fields },
`id3`: { type: `postsJson`, ...other_fields }
}
Gatsby doesn’t store a distinct collection of each child node type. Instead, it stores in Redux a single collection containing all of the children together. One key advantage of this approach is that Gatsby can create a File.children
field in GraphQL that returns all children irrespective of type. However, one important disadvantage is that creating fields such as File.childMarkdownRemark
and File.childrenPostsJson
becomes a more complex process, since no collection of each child node type is available. Gatsby also offers the ability to query a node for its child
or children
, depending on whether the parent node references one or multiple children of that type.
In Gatsby, upon defining the parent File gqlType
, the createNodeFields
API will iterate over each unique type of its children and create their respective fields. For example, given a child type named markdownRemark
, of which there is only one child node per parent File
, Gatsby will create the field childMarkdownRemark
. To facilitate queries on File.childMarkdownRemark
, we need to write a custom child resolver:
resolve
(
node
,
args
,
context
,
info
)
This resolve
function will be invoked whenever we are executing queries for each page, like the following query:
query {
file( relativePath { eq: "blog/my-blog-post.md" } ) {
childMarkdownRemark { html }
}
}
To resolve the File.childMarkdownRemark
field, Gatsby will, for each parent File
node it resolves, filter over each of its children until it encounters one of type markdownRemark
, which is then returned from the resolver function. Because that children
value is a collection of identifiers, Gatsby searches for the node by id
in the Redux nodes
namespace as well.
Before leaving the resolve
function’s logic, because Gatsby may be executing this query from within a page, whenever the node changes we need to ensure that the page is rerendered accordingly. As such, when changes in the node are detected, the resolver function calls the createPageDependency
function, passing the node identifier and the page: a field available in the context
object within the resolve
function’s signature.
Finally, once a node is created and designated a child of some parent node, that fact is noted in the child’s parent
field, whose value is the parent’s identifier. Then, the GraphQL resolver for this field searches for that parent by that id
in Redux and returns it. In the process, it also adds a page dependency through createPageDependency
to record that the page on which the query is present has a dependency on the parent node.
NOTE
For more information about how Gatsby handles plain objects or value fields that represent filepaths (such as references to JSON files on disk), consult the Gatsby documentation’s guide to schema inference for file types.
Leave a Reply