Search code examples
typescriptgraphqlgatsbyunion-types

Adding typescript tagged unions to result from graphql query in gatsby


I used Typescript tagged unions in order to type check what interface the argument of a function uses. Here are two interfaces that can each be passed into the func function below

export interface IPubArticle {
  kind: "IPubArticle"
  node: {
    fields: {
      slug: string
    }
    frontmatter: {
      title: string
      category: string
      author: string
      issue: string
      date: string
      givenRank: number
    }
  }
}

export interface IArticleSquare{
  kind: "IArticleSquare"
  node: {
    fields: {
      slug: string
    }
    frontmatter: {
      title: string
      subTitle: string
      category: string
      author: string
      featuredImage: {
        childImageSharp: IFluidObject
      }
      date: string
      givenRank: number
    }
  }
}

const func = ({data}: {data: IArticleSquare | IPubArticle}) => {
    switch (data.kind){
        case "IArticleSquare":
            ...
        case "IPubArticle":
            ...
    }
}

I am getting a problem now because apparently my graphql call doesn't match the interface because the variable kind is missing

Argument of type '{ node: { fields: { slug: string; }; frontmatter: { title: string; subTitle: string; date: string; description: string; category: string; author: string; givenRank: number; featuredImage: { childImageSharp: GatsbyImageProps; }; }; }; }[]' is not assignable to parameter of type 'IArticleSquare[]'.
  Property 'kind' is missing in type '{ node: { fields: { slug: string; }; frontmatter: { title: string; subTitle: string; date: string; description: string; category: string; author: string; givenRank: number; featuredImage: { childImageSharp: GatsbyImageProps; }; }; }; }' but required in type 'IArticleSquare'.ts(2345)
article.interface.ts(21, 3): 'kind' is declared here.

And here is my graphql query

export const pageQuery = graphql`
  query($path: string!) {
    allMarkdownRemark(
      sort: { order: DESC, fields: [frontmatter___date] }
    ) {
      edges {
        node {
          fields {
            slug
          }
          frontmatter {
            title
            subTitle
            date(formatstring: "MMMM DD, YYYY")
            category
            featuredImage {
              childImageSharp {
                fluid(maxWidth: 400) {
                  ...GatsbyImageSharpFluid
                }
              }
            }
          }
        }
      }
    }

How can I fix this? I really have no idea what to do


Solution

  • Assuming that IArticleSquareNode always has a subtitle in frontmatter and IArticleNode does not, you can write a User-Defined Type Guard (isSquareNode) to help you identify the node type.

    interface IFrontmatter {
      title: string
      category: string
      author: string
      issue: string
      date: string
      givenRank: number
    }
    
    interface IFrontmatterSquare extends IFrontmatter {
      subTitle: string
    }
    
    interface IFields {
      slug: string
    }
    
    interface IArticleNode {
      fields: IFields
      frontmatter: IFrontmatter
    }
    
    interface IArticleSquareNode {
      fields: IFields
      frontmatter: IFrontmatterSquare
    }
    
    function isSquareNode(
      node: IArticleSquareNode | IArticleNode,
    ): node is IArticleSquareNode {
      return (node as IArticleSquareNode).frontmatter.subTitle !== undefined
    }
    
    const func = (node: IArticleSquareNode | IArticleNode) => {
      if (isSquareNode(node)) {
        return 'is of type IArticleSquareNode'
      } else {
        return 'is of type IArticleNode'
      }
    }
    
    const testArticleNode = {
      fields: {
        slug: '/myslug/',
      },
      frontmatter: {
        title: 'title',
        category: 'category',
        author: 'author',
        issue: 'issue',
        date: 'date',
        givenRank: 1,
      },
    }
    
    const testArticleSquareNode = {
      fields: {
        slug: '/myslug/',
      },
      frontmatter: {
        title: 'title',
        subTitle: 'subTitle',
        category: 'category',
        author: 'author',
        issue: 'issue',
        date: 'date',
        givenRank: 1,
      },
    }
    
    console.log(func(testArticleNode)) // is of type IArticleNode
    console.log(func(testArticleSquareNode)) // is of type IArticleSquareNode