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
sx
prop -
It will extend and provide access to underlying
gatsby
Link props -
It will extend HTMLAnchorElement attributes
I’ll briefly explain those three points.
-
The
sx
prop 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 thesx
prop -
The Link component imported from
gatsby
has 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
React
import and replace it with the JSX Pragma from Theme UI which allows us to apply thesx
prop to any DOM node,div
span
or a React component etc, in this case it’s Link fromgatsby
-
Then we add an
sx
prop so we can pass in styles from the parent and spread them inside thesx
prop which is now accepted by our Link fromgatsby
. -
We also need to pass on the
to
prop since this is what makes Link fromgatsby
an internal routing link. -
Then finally spread the
rest
as we may want to pass on HTMLAnchorElement attributes, atitle
for 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 fromgatsby
and 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.state
shape, You could type as “any” for now -
Lastly “hand crank” the
sx
prop and type it asSxStyleProp
fromtheme-ui
which allows us to pass on anysx
based 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,
};