Gatsby: Accessing fields of a related record in Graphql

Created on 13 Apr 2018  Â·  17Comments  Â·  Source: gatsbyjs/gatsby

I am using Netlify CMS and I made a relationship between two records. Let's imagine I add an Author to a Post (example from Netlify website):

  - label: "Post Author"
    name: "author"
    widget: "relation"
    collection: "authors"
    searchFields: ["name", "twitterHandle"]
    valueField: "name"

How do I access fields of the Author record, when I query the Post? I imagined something like this would work:

[...]
    frontmatter {
        author {
            frontmatter {
                thumbnail
                age
            }
        }
    }

I can only access the author field, which is a string and has no further field to explore. How do I access the related record?

question or discussion

Most helpful comment

This is a request I added at the Netlify CMS project: https://github.com/netlify/netlify-cms/issues/1282

All 17 comments

Google finally graces you with an answer after you posted the question. This may be it: https://stackoverflow.com/questions/49456106/how-to-make-a-one-to-many-connection-between-netlify-cms-and-gatsby

edit: I did not get it to work. That's a lot of code for something that should be a common thing. Is there a better guide?

Hey @snirp 👋, there are some docs on doing a similar thing here. Does that help? https://www.gatsbyjs.org/docs/gatsby-config/#mapping-node-types

Are you able to add more details on which part isn't working?

Adding support for this to gatsby-plugin-netlify-cms would be cool /cc @erquhart

@m-allanson is right, this is a general data mapping problem - you'd have the same issue and steps if you created your data files manually.

@KyleAMathews it makes sense to support this via plugin, but I'd expect it to be a general mapping plugin for any Gatsby site that automates the work described in the Mapping Node Types guide. That would definitely be helpful for the community, agreed.

@m-allanson

I think things are starting to work. At least I get Fields added now to the nodes. Are these two solutions functionally identical?

// Solution 1: OnCreateNode
exports.onCreateNode = ({node, boundActionCreators,}) => {
    const { createNodeField } = boundActionCreators;

    if (node.internal.type === "MarkdownRemark"){
        createNodeField({
            node,
            name: 'test1',
            value: 'value1'
        })
    }
};

// Solution 2: sourceNodes
exports.sourceNodes = ({ boundActionCreators, getNodes, getNode }) => {
    const { createNodeField } = boundActionCreators;

    const markdownNodes = getNodes()
        .filter(node => node.internal.type === "MarkdownRemark")
        .forEach(node => {
            createNodeField({
                node,
                name: 'test2',
                value: 'value2'
            })
        })
};

I may also be a bit confused by "node" APIs. These refer strictly to the nodes in Gatsby graphs, and not to node.js, right? Why are you using exports instead of the ES6 export statement?

@erquhart
Something that would automate mapping and supports O2M (one-to-many), M2O, O2O and M2M relationships would be a great help IMHO. I might try my hand at that, but I would need some help.

but I'd expect it to be a general mapping plugin for any Gatsby site that automates the work described in the Mapping Node Types guide.

The reason I think it'd make sense to add to gatsby-plugin-netlify-cms is that plugin has (does it?) privileged information about how the user is setting up their project so potentially could automatically setup these relationships where a generic mapping plugin would need some manual setup I assume to do the same thing.

The current contract between Netlify CMS and static site generators is that Netlify CMS creates content, and the generators consume the content - there's no API's for communicating metadata like related entries. We'd be mostly inferring from the same raw content that any other plugin has access to.

So the answer to whether Netlify CMS has privileged info is "yes, but". It's there, but it's internal. Definitely something worth improving moving forward.

Noticed something strange, that may explain why I did not get it to work:

exports.onCreateNode = ({node, boundActionCreators,}) => {
    const { createNodeField } = boundActionCreators;

    if (node.internal.type === "MarkdownRemark"){
        console.log(node.internal.type);
        // Node type is "MarkdownRemark"
    }
};


exports.sourceNodes = ({ boundActionCreators, getNodes, getNode }) => {
    const { createNodeField } = boundActionCreators;

    const markdownNodes = getNodes()
        //.filter(node => node.internal.type === `MarkdownRemark`)
        .forEach(node => {
            console.log(node.internal.type +": "+ node.id);
            // Node type is "File"
        });

};

In exports.sourceNodes the node type is File, rather than MarkdownRemark. Any attempt to filter on MarkdownRemark, will come up empty.

Why is this happening?

sourceNodes is for plugins to "source" nodes — trying to access nodes in sourceNodes won't work because not all nodes have been sourced or transformed yet — which is why only File nodes are showing up as gatsby-transformer-remark hasn't had a chance to transform the File nodes to MarkdownRemark nodes. onCreateNode is for responding to node creation.

@KyleAMathews Thanks for clarifying that. That also answer my previous question whether sourceNodes and onCreateNode are functionally identical. They are not.

Note that the solution I linked in my first comment tries to make use of sourceNodes. As you explain it, that cannot possibly work because it tries to invoke specific MarkdownRemark fields (and type).

Now, I can think of two possible ways to create a "one-to-many" relationship.

  1. Use sourceNodes and try to extract specific fields from the file, without the benefit of the markdownRemark transformation. I don't know how, and it seems like an anti-pattern.
  2. Gather information from all Markdown files in onCreateNode, and in a second step add fields to the transformed nodes using createNodeField.

Would you care to comment on what I have said here @KyleAMathews ?

@snirp i think you're better off using mapping if you can as @m-allanson mentioned https://www.gatsbyjs.org/docs/gatsby-config/#mapping-node-types

Have you tried that?

I figured it wouldn't apply (only .yml and .json, no .md). Though I believe that Netlify does support files as well. Is that the route to take then?

Sorry to take up so much of your time @KyleAMathews , but you are being a great help.

Just a quick follow up for anyone reading this thread. The mappings need to be to made to JSON or YAML files, which have a "list" at the root level. This seems to be something that Netlify CMS does not allow: you always create an object in the root (or so it seems).

I am at a loss on how to get related nodes to work in Gatsby + Netlify. I'd be grateful to know when this can be solved.

This is a request I added at the Netlify CMS project: https://github.com/netlify/netlify-cms/issues/1282

Commented, I was just thinking about this the other day. We'll pick up the convo in that ticket.

@snirp once that's in place, will that be all you need to close this issue?

Now, I can think of two possible ways to create a "one-to-many" relationship.

Use sourceNodes and try to extract specific fields from the file, without the benefit of the markdownRemark transformation. I don't know how, and it seems like an anti-pattern.
Gather information from all Markdown files in onCreateNode, and in a second step add fields to the transformed nodes using createNodeField.

another option is to use onPreExtractQueries instead of sourceNodes. It worked for me

Was this page helpful?
0 / 5 - 0 ratings

Related issues

kalinchernev picture kalinchernev  Â·  3Comments

signalwerk picture signalwerk  Â·  3Comments

3CordGuy picture 3CordGuy  Â·  3Comments

brandonmp picture brandonmp  Â·  3Comments

timbrandin picture timbrandin  Â·  3Comments