T
TanStack5w ago
afraid-scarlet

How to use ValidateLinkOptions to type a React Aria Component MenuItem that uses createLink?

Hi - I have been really struggling to get types to work correctly when creating a wrapper around a RAC MenuItem component. The docs say to use ValidateLinkOptions when passing link related params...the issue is that createLink returns the actual type of the component instead of converting it to an anchor tag, which means I get DOM event mismatch errors when spreading the props on the "link" such the one below since the original MenuItem component is a div...
Type 'MouseEventHandler<HTMLAnchorElement> | undefined' is not assignable to type 'MouseEventHandler<HTMLDivElement> | undefined'
...here is the example i was going off from the docs but combined with RAC and createLink. And here's a stackblitz playground that uses this code for convenience: https://stackblitz.com/edit/github-utepv35x?file=package.json,src%2Fmain.tsx,src%2Froutes%2Findex.tsx This one has stumped me all day, thanks to anyone in advance that can help here
import {
createLink,
RegisteredRouter,
ValidateLinkOptions,
} from '@tanstack/react-router';
import * as React from 'react';
import { MenuItem } from 'react-aria-components';

const MenuItemLink = createLink(MenuItem);

export interface HeaderLinkProps<
TRouter extends RegisteredRouter = RegisteredRouter,
TOptions = unknown
> {
title: string;
linkOptions: ValidateLinkOptions<TRouter, TOptions>;
}

export function HeadingLink<TRouter extends RegisteredRouter, TOptions>(
props: HeaderLinkProps<TRouter, TOptions>
): React.ReactNode;
export function HeadingLink(props: HeaderLinkProps): React.ReactNode {
return (
<>
<h1>{props.title}</h1>
<MenuItemLink {...props.linkOptions} />
</>
);
}
import {
createLink,
RegisteredRouter,
ValidateLinkOptions,
} from '@tanstack/react-router';
import * as React from 'react';
import { MenuItem } from 'react-aria-components';

const MenuItemLink = createLink(MenuItem);

export interface HeaderLinkProps<
TRouter extends RegisteredRouter = RegisteredRouter,
TOptions = unknown
> {
title: string;
linkOptions: ValidateLinkOptions<TRouter, TOptions>;
}

export function HeadingLink<TRouter extends RegisteredRouter, TOptions>(
props: HeaderLinkProps<TRouter, TOptions>
): React.ReactNode;
export function HeadingLink(props: HeaderLinkProps): React.ReactNode {
return (
<>
<h1>{props.title}</h1>
<MenuItemLink {...props.linkOptions} />
</>
);
}
StackBlitz
Router Quickstart File Based Example - StackBlitz
Run official live example code for Router Quickstart File Based, created by Tanstack on StackBlitz
3 Replies
optimistic-gold
optimistic-gold2w ago
did you get this resolved? if not, what didnt work with ValidateLinkOptions here?
afraid-scarlet
afraid-scarletOP2w ago
Sorry for the delay, I did not get this resolved. I actually opened a ticket in React Aria Components under the same vein since I'm still dealing with this. Feel free to check out the ticket here. There's an additional repro there https://github.com/adobe/react-spectrum/issues/9161 Quoting myself: " I think it has to do with type "Contravariance" where the parameter type from RAC of FocusableElement is TOO generic and when we pass in a more specific type like HTMLAnchorElement, typescript doesn't like this..."
GitHub
Incompatible types when using MenuItem and Tanstack Router createLi...
Provide a general summary of the issue here Hi! I have run into a type incompatibility issue when using RAC MenuItem with Tanstack Router&#39;s createLink and ValidateLinkOptions helpers. Tanstack ...
afraid-scarlet
afraid-scarletOP2w ago
This code block explains why it errors though, I think
// What I have:
type SpecificHandler = (e: MouseEvent<HTMLAnchorElement>) => void;

// The RAC Link Expects a FocusableElement:
type GenericHandler = (e: MouseEvent<FocusableElement>) => void;

----- EXAMPLE OF WHERE THIS FAILS --------

// My handler expects HTMLAnchorElement specifically
const yourHandler: SpecificHandler = (e) => {
console.log(e.currentTarget.href); // I can access anchor props here
};

// If TypeScript allowed this assignment...
const genericHandler: GenericHandler = yourHandler;

// ...then I could call it with a button instead
const buttonEvent = new MouseEvent<HTMLButtonElement>(...);
genericHandler(buttonEvent); This is a problem, since Button doesn't have .href. Which is why TS errors out
// What I have:
type SpecificHandler = (e: MouseEvent<HTMLAnchorElement>) => void;

// The RAC Link Expects a FocusableElement:
type GenericHandler = (e: MouseEvent<FocusableElement>) => void;

----- EXAMPLE OF WHERE THIS FAILS --------

// My handler expects HTMLAnchorElement specifically
const yourHandler: SpecificHandler = (e) => {
console.log(e.currentTarget.href); // I can access anchor props here
};

// If TypeScript allowed this assignment...
const genericHandler: GenericHandler = yourHandler;

// ...then I could call it with a button instead
const buttonEvent = new MouseEvent<HTMLButtonElement>(...);
genericHandler(buttonEvent); This is a problem, since Button doesn't have .href. Which is why TS errors out

Did you find this page helpful?