Nested sort in GraphQL

Viewed 2186

I'm using Contentful as a CMS, wherein I have the content models "Category" and "Subcategory". Each subcategory has a category as its parent. Both models have a property called order which I set within the CMS to decide the order in which they will appear on the navigation.

Here's my GraphQL query:

query {
  allContentfulCategory(sort: {fields: [order, subcategory___order]}) {
    edges {
      node {
        order
        title
        subcategory {
          order
          title
        }
      }
    }
  }
}

The first sort works correctly and the query outputs my categories by the order field, rather than in reverse-creation order which seems to be the default output from Contentful (the newest categories show first).

However, my second sort of subcategory___order, which was inspired by this blog post, does not evaluate. In other words, the subcategories (per Category) show up in reverse order so that the top item has an order of 8, and it decreases to 7, and 6, and so on.

For example, say I had category "Animals" with order 2, and "Trees" with order 1. My content in reverse-creation order: "Cat" (order 3), "Dog" (order 1), "Chicken" (order 2), "Birch" (order 2), "Oak" (order 3), and "Willow" (order 1).

These will come through the query like so:

Trees (1):
  Birch (2)
  Oak (3)
  Willow (1)
Animals (2):
  Cat (3)
  Dog (1)
  Chicken (2)

I can't find a way to make the GraphQL query perform both sort commands simultaneously so that my data is ordered according to the order I set in the CMS.

So many thanks in advance!

Edit: Here's a component solution:

Within each category node that comes from my query, I can access the subcategories and their order in an array.

const categories = data.allContentfulCategory.edges
    {categories.map((category) => {
        return (
            <div>
                {category.node.subcategory && category.node.subcategory.sort((a, b) => {return a.order - b.order}).map((subcategory) => {
                    return (
                        <div>
                            <p>{subcategory.title}</p>
                        </div>
                    )
                })}
            </div>
         )
      })}

The key element here is how I sort the array by subcategory order within the component:

category.node.subcategory.sort((a, b) => {return a.order - b.order})

To be clear, this is not a satisfying solution, nor does it answer my question about how I might write multiple GraphQL sorts. But this code does do what I wanted with regards to organising the subcategories.

1 Answers

You are querying category and not subcategory:

query {
  allContentfulCategory(sort: {fields: [order, subcategory___order]}) {
    edges {
      node {
        order
        title
        subcategory {
          order
          title
        }
      }
    }
  }
}

In the query above, only the returned array data.allContentfulCategory.edges is sorted. Gatsby won't sort the data nested in category.subcategory.

Further more, the second field you specified (subcategory___order) will only be considered if the first field (order) is the same. Since each category has a unique order, subcategory will never be used.

If it's possible for you to query for something like allContentfulSubcategory instead, it will work.

Otherwise, you can move the sort logic to build time instead by using createSchemaCustomization. I don't know what your schema look like so this is just an example:

// gatsby-node.js
exports.createSchemaCustomization = ({ actions, schema }) => {
  const { createTypes } = actions
  const typeDefs = [
    schema.buildObjectType({
      name: "ContentfulCategory",
      fields: {
        subcategory: {
          type: "[ContentfulSubcategory]",
          resolve: (src) => {
            const subcategories = src.subcategory
            return subcategories.sort()
          }
        },
      },
      interfaces: ["Node"],
      extensions: { infer: true },
    }),
  ]
  createTypes(typeDefs)
}

If you need to use createSchemaCustomization, see the Gatsby docs on the topic here.

The docs' info could be overwhelming, I also wrote a bit about this API here. It is more about field extension, but the resolver function is basically the same.

Related