I have a react component which either takes the to
(react-router-dom prop) or href
prop depending on where I want to redirect the user; whether it's within the app or to an external url. Typescript complains about my type with
Property 'to' does not exist on type 'LinkType'.
Property 'href' does not exist on type 'LinkType'.
This is how I defined my type
import React from 'react';
import { Link } from 'react-router-dom';
interface LinkBase {
label: string;
icon?: JSX.Element;
}
interface RouterLink extends LinkBase {
to: string;
}
interface HrefLink extends LinkBase {
href: string;
}
type LinkType = RouterLink | HrefLink;
export function NavLink(props: LinkType) {
const { to, label, icon, href } = props; // TS complains about those to and href
return(
{to ? (
<Link to={to}>{label}</Link>
) : (
<a href={href}>{label}</a>
)}
);
}
Because LinkType
is a union, HrefLink
and RouterLink
are both assignable to LinkType
. HrefLink
does not have "to" on it, and RouterLink does not have "href" on it. Therefore typescript can not guarantee that either of those properties exists on the union. You can use a type guard to do what you're trying to while also satisfying TypeScript.
import React from 'react';
import { Link } from 'react-router-dom';
interface LinkBase {
label: string;
icon?: JSX.Element;
}
interface RouterLink extends LinkBase {
to: string;
}
interface HrefLink extends LinkBase {
href: string;
}
type LinkType = RouterLink | HrefLink;
function isRouterLink(v: unknown): v is RouterLink {
return v && Object.prototype.hasOwnProperty.call(v, 'to');
}
function isHrefLink(v: unknown): v is HrefLink {
return v && Object.prototype.hasOwnProperty.call(v, 'href');
}
export function NavLink(props: LinkType) {
const { label, icon } = props;
if (isRouterLink(props)) {
const { to } = props; // typechecks now
return (<> ... </>)
}
if (isHrefLink(props)) {
const { href } = props;
return (<> ... </>);
}
return (<> ... </>);
}
Another option would be using a "tagged" union to get around the need of a type guard function. These go by some different names depending on who you're talking to. Other names you can find these by are "discriminating union" and "disjoint union" to name a couple.
interface LinkBase {
label: string;
icon?: JSX.Element;
}
interface RouterLink extends LinkBase {
type: 'router';
to: string;
}
interface HrefLink extends LinkBase {
type: 'href';
href: string;
}
type LinkType = RouterLink | HrefLink;
export function NavLink(props: LinkType) {
const { label, icon } = props;
if (props.type === 'router') {
const { to } = props; // typechecks now
return (<> ... </>);
}
if (props.type === 'href') {
const { href } = props;
return (<> ... </>);
}
return (<> ... </>);
}
So then if you inspect the type of LinkType['type']
you'll see 'router' | 'href'
, and TypeScript can automatically narrow the type for you if you do an if statement on this type property of LinkType.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With