TypeScript Theme UI Link
In a previous post; gatsby-or-theme-ui-link i talked about how to cast a
Theme UI Link as gatsby Link… and that will work fine in JavaScript but as i’ve discovered when you attempt to
do the same thing in TypeScript you’ll run into problems.
Not big problems, it’s just TypeScript doing it’s job and letting you know that you’ve neglected to tell your code something important.
That was the case with me so i’m now going to elaborate on my previous post and explain how to create your own custom Link component that meets the below Acceptance Criteria
-
It will accept an
sxprop -
It will extend and provide access to underlying
gatsbyLink props -
It will extend HTMLAnchorElement attributes
I’ll briefly explain those three points.
-
The
sxprop is part of the Theme UI JSX pragma and allows you to style any DOM node or component as if it were a component from Theme UI using thesxprop -
The Link component imported from
gatsbyhas super powers, it has some functional props and is location aware… we don’t wanna lose this functionality. -
It’s still an
<a>so should still behave like one.
If you need a little more guidance i’ve made a bare bones repo which you can see on GitHub PaulieScanlon/typescript-theme-ui-link and most things i’ll be explaining in this post can be seen in the MrLinky.tsx file
Recap
To recap this is where we left off with gatsby-or-theme-ui-link
// some-file.js
import React from 'react';
import { Link as GatsbyLink } from 'gatsby';
import { Link } from '@theme-ui/components';
const SomeComponent = () => {
return (
<>
<Link sx={{ color: 'primary' }} to='/' as={GatsbyLink}>
Back to home
</Link>
<Link sx={{ color: 'secondary' }} href='https://www.paulie.dev'>
https://www.paulie.dev
</Link>
</>
);
};
If you try and do that in TypeScript you’ll get an error because to isn’t a recognized prop for Link.
Remember Link is effectively still Link from theme-ui and not quite yet a Link from gatsby
So what can we do about it? 🤔
MrLinky
One way, and there may well be more ways to accomplish this but…
I created a new component and called it MrLinky … because it’s confusing having all these components called Link.
// MrLinky.tsx
/** @jsx jsx */
import { jsx, SxStyleProp } from "theme-ui";
import { FunctionComponent, AnchorHTMLAttributes } from "react";
import { Link, GatsbyLinkProps } from "gatsby";
export interface MrLinkyProps
extends AnchorHTMLAttributes<HTMLAnchorElement>,
GatsbyLinkProps<{}> {
/** Theme UI JSX pragma */
sx?: SxStyleProp;
}
export const MrLinky: FunctionComponent<MrLinkyProps> = ({
children,
to,
sx,
...rest
}) => (
<Link
to={to}
sx={{ ...sx }}
{...(rest as AnchorHTMLAttributes<HTMLAnchorElement>)}
>
{children}
</Link>
);
So what’s going on here then?
-
First off we ditch the
Reactimport and replace it with the JSX Pragma from Theme UI which allows us to apply thesxprop to any DOM node,divspanor a React component etc, in this case it’s Link fromgatsby -
Then we add an
sxprop so we can pass in styles from the parent and spread them inside thesxprop which is now accepted by our Link fromgatsby. -
We also need to pass on the
toprop since this is what makes Link fromgatsbyan internal routing link. -
Then finally spread the
restas we may want to pass on HTMLAnchorElement attributes, atitlefor example
The tricky part is typing all of these props. So let’s have a look at the interface
interface MrLinkyProps extends AnchorHTMLAttributes<HTMLAnchorElement>, GatsbyLinkProps<{}> {
/** Theme UI JSX pragma */
sx?: SxStyleProp;
}
-
First we have to extend the interface to include
AnchorHTMLAttributes<HTMLAnchorElement>which will be handy if we want to pass on HTMLAnchorElement attributes. -
Then include
GatsbyLinkProps<{}>This comes fromgatsbyand is what gives the Link it’s super powers.I’ll revisit the
GatsbyLinkProps<{}>in another post as it relates to a TypeScript generic used to type thelocation.stateshape, You could type as “any” for now -
Lastly “hand crank” the
sxprop and type it asSxStylePropfromtheme-uiwhich allows us to pass on anysxbased styes which will get applied because the entire component is now JSX Pragma enabled. 😅
Usage
And here you go, a new Link component that does all the things Link from gatsby does and all the things that Link from
theme-ui does… and it has a cooler name 😎
<MrLinky to='/some-internal-link/'>Sone internal link</Link>
<MrLinky to='/some-internal-link/' title='Some Title'>Sone internal link</Link>
<MrLinky to='/some-internal-link/' sx={{color: "primary"}}>Sone internal link</Link>
<MrLinky to='/some-internal-link/' activeClassName='some-active-class'>Sone internal link</Link>
Warning ⚠️
There is one caveat to using this method that i think you should know about. Theme UI also has a variant prop but
since MrLinky is actually a Gatsby link we won’t be able to use variant as a prop like this 👇
<MrLinky variant='links.navigation' />
…but we can still take advantage of the variant prop by passing it on via the sx prop e.g 👇
<MrLinky to="/some-internal-link/" sx={{ color: 'primary', variant: 'links.navigation' }}>
Sone internal link
</Link>
You might also want to spread some default styles inside the component to ensure it points at styles.a by default.
// MrLinky.tsx
/** @jsx jsx */
import { jsx, SxStyleProp } from "theme-ui";
import { FunctionComponent, AnchorHTMLAttributes } from "react";
import { Link, GatsbyLinkProps } from "gatsby";
import theme from "../../gatsby-plugin-theme-ui"; /** => import the theme **/
export interface MrLinkyProps
extends AnchorHTMLAttributes<HTMLAnchorElement>,
GatsbyLinkProps<{}> {
/** Theme UI JSX pragma */
sx?: SxStyleProp;
}
export const MrLinky: FunctionComponent<MrLinkyProps> = ({
children,
to,
sx,
...rest
}) => (
<Link
to={to}
sx={{ ...theme.styles.a, ...sx }} /** => Add default styles before the sx **/
{...(rest as AnchorHTMLAttributes<HTMLAnchorElement>)}
>
{children}
</Link>
);
It’s a small shortcoming in my opinion and keeping all the Gatsby Link super powers is well worth the sacrifice.
End Warning ⚠️
MrsLinky
For completeness i’ve also created a MrsLinky component which pretty much does the same thing but is straight up
JavaScript and uses PropTypes just to give you some indication of what the props are.
The to prop is marked as isRequired as it’s also required by Link from gatsby under the hood. The remaining props
are marked as good old any because this is JavaScript so you can do whatever you like!
// MrsLinky.js
/** @jsx jsx */
import { jsx } from 'theme-ui';
import PropTypes from 'prop-types';
import { Link } from 'gatsby';
export const MrsLinky = ({ children, to, sx, ...rest }) => (
<Link to={to} sx={{ ...sx }} {...rest}>
{children}
</Link>
);
MrsLinky.propTypes = {
/** Gatsby internal route */
to: PropTypes.string.isRequired,
/** Theme UI JSX pragma */
sx: PropTypes.any,
/** Inherited props from AnchorHTMLAttributes and GatsbyLinkProps */
rest: PropTypes.any,
};