Extending Gatsby Link in Typescript
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:
The Gatsby Link Typescript problem
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.
How to extend Gatsby’s GatsbyLinkProps correctly in Typescript
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.