Gatsby: Isolate data in a nested component graphql query

Created on 2 Oct 2017  路  10Comments  路  Source: gatsbyjs/gatsby

Hi, I am having some trouble with filtering / isolating a graphql query for a component that is a child of a dynamically created page. (Including config details at bottom)

I am using gatsby-source-contentful to create the GraphQL schema. Right now, I create some pages dynamically in gatsby-node.js, and pass a context variable through createPage to my GraphQL query so I can isolate the component's data.

This works nicely, but I run into trouble because each one of these components has a dynamic list of "page block" children. Basically editors in Contentful can add however many of these page blocks they want from a set of them (Header Block, Video Block, Quote Block, etc) such that they can create a unique page layout.

The problem I am having is being able to query the data for those individual components using GraphQL. The gatsby-source-contentful plugin adds what I think is a backwards reference to each of those content types (feature_pages__new__ for example) but I cannot pass an argument to this field or filter on the content type for this field. I also don't know if I can pass a GraphQL variable to another component through a property.

Right now I have to load all of the assets for each content type in the parent component (the page) query, search for the right one based on a field such as id or route in JS, and then pass that data to the child component.

I don't think that is a very elegant approach. I am doing data querying in two different formats (JS & GraphQL), and am having to load a lot of extraneous data in the parent component (every node for every page block content type).

Does Gatsby have an opinion for this sort of use case yet? I think one of the following would work: passing a child component a GraphQL variable for its own query (best), filter based on a backwards reference (not great), pass a backwards reference field an argument so I can pass all the data through to the child (not great).

Thanks!!

As a side note, does Gatsby support GraphQL queries from children components of dynamically created pages? They seem to not return anything to the child component's ({ data }) parameter.


node: 8.5.0
OS: macOS 10.12.6
gatsby: ^1.9.42
gatsby-source-contentful: ^1.3.13

gatsby-node.js, creating feature pages here, which have child components I want to pass graphql variables to so I can query in them too:

const path = require('path');

exports.createPages = function({ graphql, boundActionCreators }) {
  const { createPage } = boundActionCreators
    return new Promise(function (resolve, reject) {
        graphql(`{
          allContentfulFeaturePagesNew {
            edges {
              node {
                path
              }
            }
          }
        }
    `).then(function (result) {
        result.data.allContentfulFeaturePagesNew.edges.map(({ node }) => {
          createPage({
            path: `/features${node.path}`,
            component: path.resolve(`./src/components/Feature/index.js`),
            context: {
              // used to isolate data in graphql query
              path: node.path
            },
          })
        })
        resolve()
      })
    })
  }

Example of back reference (feature_pages_new__) that I can't filter/search on:

allContentfulFpHeader {
    edges {
      node {
        headline
        subhead
        feature_pages__new__ {
          path
        }
      }
    }
  }

Most helpful comment

I was trying to solve the same problem so thanks, this was helpful! 馃憤
The issue for us is that we don't know what blockTypes might be added by the content authors, so we need to account for any possibility.
But, when we add a blockType that hasn't been added to any page yet, we get the following error:

Fragment cannot be spread here as objects of type
\"Union_entries___NODE_ContentfulTypeA__ContentfulTypeB__ContentfulTypeC\" 
can never be of type \"ContentfulTypeD\"."

I currently have one test page that only has block types A, B, and C, and the query fails because I have added type D to the fragment but that content type was not added to the page.

The inline fragment:

blocks {
      __typename
      ... on ContentfulTypeA {
        #content type that exist in the test page
      }
      ... on ContentfulTypeB {
        #content type that exist in the test page
      }
      ... on ContentfulTypeC {
        #content type that exist in the test page
      }
      ... on ContentfulTypeD {
        #content type that does NOT exist in the test page
      }
    }

Does this only work if all the content types are used on any given page?

All 10 comments

Not entirely sure I understand your data model but when the source plugin creates links for the foreign references, it only links the content that is also linked in Contentful. So no filtering is necessary.

E.g. in the example contentful site, this query returns for each brand only the products linked to that brand.

{
  allContentfulBrand {
    edges {
      node {
        product {
          id
        }
      }
    }
  }
}

Hm, so here is the query I run for the page component:

contentfulFeaturePagesNew(path: { eq: $path }) {
    ...
    pageBlocks {
      __typename
    }
  }
}

but that __typename is the only field that is available within the pageBlocks object. I can't access the actual node/asset, just the content type name. So I end up with a list of the content types linked to by that page, but no way to identify the actual content.

In our Contentful model, the Feature Page has a field, Page Blocks which is set up as an array of links to other content types. I thought that was Contentful's idiomatic way to set up a container for smaller modules, based on their fashion blog example, though perhaps there is preferable way that results in the gatsby-source-contentful schema pulling the rest of the Page Block (child) fields through?

Looking through the demo Contentful project, it looks like Product (child) references the Brand (parent), whereas on my project its the inverse, the Page (parent) references many Page Blocks (child).

So ideally, the child would reference the parent for the gatsby-source-contentful schema? And then you can just pass the JS object through from the parent component to the child component using a prop? Is that preferable to passing an ID or something to the child and letting it run its own graphql query?

Ah I see the confusion.

For mixed-type fields like that, gatsby-source-contentful creates a "union type" field for the referenced entities.

So you need to make queries like this:

http://graphql.org/learn/queries/#inline-fragments

This blog post looks like it might provide some helpful context on this part of GraphQL's design https://medium.com/the-graphqlhub/graphql-tour-interfaces-and-unions-7dd5be35de0d

OK, yeah that works! As a hint for others confused by Union types, creating an inline fragment for each type that can come back does the trick.

# pageBlocks has an array of links to various content types

pageBlocks {
  __typename
  ... on blockTypeA {
    # fields
  }
  ... on blockTypeB {
    # fields
  }
  # and so on for each __typename returned by pageBlocks
}

Sweeeet! Yeah and it keeps the order as well which is great. It's also nice as you can have a component for each type and switch on the __typename.

I was trying to solve the same problem so thanks, this was helpful! 馃憤
The issue for us is that we don't know what blockTypes might be added by the content authors, so we need to account for any possibility.
But, when we add a blockType that hasn't been added to any page yet, we get the following error:

Fragment cannot be spread here as objects of type
\"Union_entries___NODE_ContentfulTypeA__ContentfulTypeB__ContentfulTypeC\" 
can never be of type \"ContentfulTypeD\"."

I currently have one test page that only has block types A, B, and C, and the query fails because I have added type D to the fragment but that content type was not added to the page.

The inline fragment:

blocks {
      __typename
      ... on ContentfulTypeA {
        #content type that exist in the test page
      }
      ... on ContentfulTypeB {
        #content type that exist in the test page
      }
      ... on ContentfulTypeC {
        #content type that exist in the test page
      }
      ... on ContentfulTypeD {
        #content type that does NOT exist in the test page
      }
    }

Does this only work if all the content types are used on any given page?

Yeah the schema is created from your data so if the data isn't there you can't query it.

I should add though that only one instance of a content type needs the data for you to query it.

Got it! Thanks for clarifying! 馃檪

In case anyone faces the same issue.

blocks {
  __typename
  ... on Node {
    ... on ContentfulTypeA {
      #content type that exist in the test page
    }
    ... on ContentfulTypeB {
      #content type that exist in the test page
    }
    ... on ContentfulTypeC {
      #content type that exist in the test page
    }
    ... on ContentfulTypeD {
      #content type that does NOT exist in the test page
    }
  }
}
Was this page helpful?
0 / 5 - 0 ratings

Related issues

ghost picture ghost  路  3Comments

magicly picture magicly  路  3Comments

jimfilippou picture jimfilippou  路  3Comments

ferMartz picture ferMartz  路  3Comments

theduke picture theduke  路  3Comments