1. Home
  2. Web Development
  3. Extending Gatsby Link in Typescript

Extending Gatsby Link in Typescript

Share

If you are having trouble extending the Gatsby Link component and GatsbyLinkProps in Typescript, this is the article for you. Keep reading because in the next sections we will explain why and how to solve the issue.

In this article we explain:

I was trying to extend the properties of GatsbyLinkProps to create my custom React component, when at some point I found myself in front of this very strange issue:

No overload matches this call.
  Overload 1 of 2, '(props: GatsbyLinkProps<any> | Readonly<GatsbyLinkProps<any>>): GatsbyLink<any>', gave the following error.
    Type '{ children: ReactNode; activeClassName?: string | undefined; activeStyle?: object | undefined; onClick?: ((event: MouseEvent<HTMLAnchorElement, MouseEvent>) => void) | undefined; ... 266 more ...; to: string; }' is not assignable to type 'IntrinsicClassAttributes<GatsbyLink<any>>'.
      Types of property 'ref' are incompatible.
        Type 'LegacyRef<HTMLAnchorElement> | undefined' is not assignable to type 'LegacyRef<GatsbyLink<any>> | undefined'.
          Type '(instance: HTMLAnchorElement | null) => void' is not assignable to type 'LegacyRef<GatsbyLink<any>> | undefined'.
            Type '(instance: HTMLAnchorElement | null) => void' is not assignable to type '(instance: GatsbyLink<any> | null) => void'.
              Types of parameters 'instance' and 'instance' are incompatible.
                Type 'GatsbyLink<any> | null' is not assignable to type 'HTMLAnchorElement | null'.
                  Type 'GatsbyLink<any>' is missing the following properties from type 'HTMLAnchorElement': charset, coords, download, hreflang, and 259 more.
  Overload 2 of 2, '(props: GatsbyLinkProps<any>, context: any): GatsbyLink<any>', gave the following error.
    Type '{ children: ReactNode; activeClassName?: string | undefined; activeStyle?: object | undefined; onClick?: ((event: MouseEvent<HTMLAnchorElement, MouseEvent>) => void) | undefined; ... 266 more ...; to: string; }' is not assignable to type 'IntrinsicClassAttributes<GatsbyLink<any>>'.
      Types of property 'ref' are incompatible.
        Type 'LegacyRef<HTMLAnchorElement> | undefined' is not assignable to type 'LegacyRef<GatsbyLink<any>> | undefined'.ts(2769)

This is an incomprehensible error message, isn’t it? After some research, I finally found that the problem is related to the fact that Gatsby’s Link component uses the @reach/router module internally. This means that Link is exported with the forwardRef property which gives access to the ref property, which precisely causes this incompatibility in Typescript:

Types of property 'ref' are incompatible.

The simple and painless solution is to omit the ref property. To do so, we just need to use Omit<GatsbyLinkProps<{}>, 'ref'> and the ref property will be, indeed, omitted. Below is an example of implementing a custom component that extends Gatsby’s Link:

import React from 'react'
import { GatsbyLinkProps, Link as GLink } from 'gatsby'
import clx from 'classnames'
import styles from './Button.module.css'

type CustomGatsbyLinkProps = Omit<GatsbyLinkProps<{}>, 'ref'>

const Link: React.FC<CustomGatsbyLinkProps> = ({
  className,
  children,
  ...props
}) => (
  <GLink className={clx(styles.link, className)} {...props}>
    {children}
  </GLink>
)

export default Link

In this case, I created a custom Link component extending Gatsby’s Link to assign it a static CSS class.

Also, if you want to add more properties to the React component, in Typescript you simply need to declare the LinkProps as an interface:

interface CustomGatsbyLinkProps extends Omit<GatsbyLinkProps<{}>, 'ref'> {
  active: boolean
}

Take in mind that, if you are using Typescript 4, you can’t use {} as a type. In this case, you need to use Record<string, unknown>. From the example below, the final result will be:

interface CustomGatsbyLinkProps
  extends Omit<GatsbyLinkProps<Record<string, unknown>>, 'ref'> {
  active?: boolean
}

Conclusion

That’s it! I hope this article has been helpful to anyone who, like me, had this problem. Special thanks to onestopjs who first suggested the resolution to this problem. For more information, you can visit the issue on Github.

If you like our post, please share it: