21

I am trying to write a query to retrieve an object with the property linkedCards that contains an array of objects with different schemas.

I have 3 different schemas (built in Contentful):

CardA example:

{
    id: 42,
    productName: 'Laptop',
    price: 999
}

CardB example:

{
    id: 999,
    title: 'Buy our refurbished Laptops today!'
}

CardC example:

{
    id: 100,
    linkedCards: [
        {
            id: 42,
            productName: 'Laptop',
            price: 999
        },
        {
            id: 999,
            title: 'Buy our refurbished Laptops today!'
        }
    ]
}

Query:

allCardC() {
    nodes {
        linkedCards {
            id
            title
        }
    }
}

When I try to run the following GraphQL query I get "Cannot query field "title" on type "CardACardBUnion". Did you mean to use an inline fragment on "CardA" or "CardB"?"

Is there a built-in way to do this or can I use the ids of CardA & CardB somehow? Perhaps have one query to get the ids of the cards in linkedCards and another query to get said cards?

Christoph Anderson
  • 617
  • 1
  • 6
  • 10

2 Answers2

37

As the error indicates, you need to use an inline fragment when querying a field that resolves to a union:

allCardC {
    nodes {
        linkedCards {
            ... on CardA {
              id
              productName
              price
            }
            ... on CardB {
              id
              title
            }
        }
    }
}

Fragments can be defined inline within a selection set. This is done to conditionally include fields based on their runtime type.

Unlike interfaces or regular object types, unions do not specify any particular fields, only the types that make up the union. That means a selection set for a field that returns a union must always use fragments to conditionally specify the fields depending on the actual type that the field resolves to.

It's like saying, "if this is the actual type of the returned object, request these fields".

Daniel Rearden
  • 80,636
  • 11
  • 185
  • 183
8

You may find it useful to use a GraphQL interface to specify the fields that every card type has in common.

interface Card {
  id: ID!
}
# type CardA implements Card { ... }
type CardB implements Card {
  id: ID!
  title: String!
}
type CardC implements Card {
  id: ID!
  linkedCards: [Card!]!
}

As @DanielRearden's answer suggests you still need to use (inline) fragments to select fields that are specific to one of the card types, but now that you know every card has an id field, you can select that directly.

allCardC {
    nodes {
        linkedCards {
            id
            ... on CardB { title }
        }
    }
}
David Maze
  • 130,717
  • 29
  • 175
  • 215