New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
ToOptions doesnt't satisfy <Link> props. #1271
Comments
ToOptions wouldn't satisfy all the possible options that a Try this. import { LinkProps, Registered router } from "@tanstack/react-router"
interface MyLinkProps {
linkProps: LinkProps<RegisteredRouter['routeTree']>
}
...
<Link params {...props.linkProps}> |
export type LinkProps<TRouteTree extends AnyRoute = RegisteredRouter['routeTree'], TFrom .... And it doesn't work either. Type '{ children: ((string | number | boolean | ReactElement<any, string | JSXElementConstructor<any>> | Iterable<ReactNode> | ReactPortal | ((state: { ...; }) => ReactNode)) & (string | ... 4 more ... | ReactPortal)) | null | undefined; ... 287 more ...; onTransitionEndCapture?: TransitionEventHandler<...> | undefined; }' is not assignable to type 'IntrinsicAttributes & ({ to?: ToPathOption<Route<any, "/", "/", string, "__root__", RootSearchSchema, RootSearchSchema, RootSearchSchema, ... 12 more ..., any>, string, "/" | ... 3 more ... | "/posts/$id/"> | undefined; hash?: true | ... 1 more ... | undefined; state?: true | ... 1 more ... | undefined; from?: Route...'.
Type '{ children: ((string | number | boolean | ReactElement<any, string | JSXElementConstructor<any>> | Iterable<ReactNode> | ReactPortal | ((state: { ...; }) => ReactNode)) & (string | ... 4 more ... | ReactPortal)) | null | undefined; ... 287 more ...; onTransitionEndCapture?: TransitionEventHandler<...> | undefined; }' is not assignable to type 'MakePathParamOptions<true | ParamsReducer<{} | {} | { id: string; } | ({ id: string; } & {}), {} | {} | ({ id: string; } & {})>>'.
Types of property 'params' are incompatible.
Type 'true | ((current: {} | {} | { id: string; } | ({ id: string; } & {})) => never) | undefined' is not assignable to type 'true | ParamsReducer<{} | {} | { id: string; } | ({ id: string; } & {}), {} | {} | ({ id: string; } & {})>'.
Type 'undefined' is not assignable to type 'true | ParamsReducer<{} | {} | { id: string; } | ({ id: string; } & {}), {} | {} | ({ id: string; } & {})>'. |
@schiller-manuel did anything change here? LinkProps used to work just fine. |
Adding {...toOptions}
from={"/"} satisfies props.. But this will overwrite the |
Not sure if it covers all your use-cases, but I was able to get a Link wrapper working with something like this. Personally I find it cleaner passing the whole route object as opposed to just the // helper types
type AtLeastOne<T, U = { [K in keyof T]: Pick<T, K> }> = Partial<T> & U[keyof U];
type ExcludeEmpty<T> = T extends AtLeastOne<T> ? T : never;
// seem to be as safe/strict as the props of the tanstack-provided Link component
type RouteParams<T extends AnyRoute = RegisteredRouter['routeTree']> = ExcludeEmpty<
T['types']['params']
> extends never
? { params?: never }
: { params: T['types']['params'] };
type RouteSearch<T extends AnyRoute = RegisteredRouter['routeTree']> = ExcludeEmpty<
T['types']['searchSchema']
> extends never
? { search?: never }
: {
search?: T['types']['searchSchema'] | ((args: T['types']['fullSearchSchema']) => T['types']['searchSchema']);
};
// component props
type MyLinkProps<T extends AnyRoute = RegisteredRouter['routeTree']> = {
route: T;
children: React.ReactNode;
className?: string;
disabled?: boolean;
} & RouteParams<T> &
RouteSearch<T>;
// custom Link component
function MyLink<T extends AnyRoute = RegisteredRouter['routeTree']>({
route,
children,
...other
}: MyLinkProps<T>) {
return (
<Link<AnyRoute> to={route?.to} {...other}>
{children}
</Link>
);
}
As a note - there might be a better way to skin this cat, but the docs seem to be pretty limited on the topic. Just figured I would share what worked for me in case you were still stuck here. |
So turns out this is what works. const MyLink = <
TRouteTree extends AnyRoute = RegisteredRouter['routeTree'],
TFrom extends RoutePaths<TRouteTree> | string = string,
TTo extends string = '',
TMaskFrom extends RoutePaths<TRouteTree> | string = TFrom,
TMaskTo extends string = '',
>(
props: React.PropsWithoutRef<
LinkProps<TRouteTree, TFrom, TTo, TMaskFrom, TMaskTo> &
Omit<React.HTMLProps<'a'>, 'children' | 'preload'>
>
) => <Link {...props} /> |
can we make use of this in I don't want to expose all of these generics as public API, otherwise we cannot modify them without causing breaking changes |
So did anyone inspected - what's wrong with ToOptions? In my optinion - that is a user friendly type to be used in this place! |
This code was working
But I updated from 1.16.6 to 1.20.1 and it does not work anymore. I got the error : |
I've seen many similar issues/discussions and stackoverflow posts. At this point, we could really use some official document / examples on how to wrap components in a type safe way. Even the customized solutions that work, only the |
Yeah, this is basically the same as #1194. I proposed contributing a solution but there was no response from the maintainers at that time. Here's the workaround I use currently: (It uses a wrapper function to create the link options, so it can be used as a regular object. This also works for passing it into a component without requiring a ton of boilerplate generics everywhere.) export type RouterLinkProps = Parameters<RegisteredRouter["navigate"]>[0];
/**
* Validate a router link as type-safe and return a generic {@link RouterLinkProps}.
* @example link({ to: "/view/$id", params: { id: "1" } })
*/
export function link<
TRouteTree extends AnyRoute = RegisteredRouter["routeTree"],
TFrom extends RoutePaths<TRouteTree> | string = string,
TTo extends string = "",
TMaskFrom extends RoutePaths<TRouteTree> | string = TFrom,
TMaskTo extends string = "",
>(options: UseLinkPropsOptions<TRouteTree, TFrom, TTo, TMaskFrom, TMaskTo>) {
return options as RouterLinkProps;
} The type can also be spread directly: const someLink = link({ .... });
...
function MyComponent({ someLink } : { someLink: RouterLinkProps }) {
return <Link {...someLink} />;
} |
I tried to update my typings based on @jaens example, but unfortunately it does not work in function MyLink<
TRouteTree extends AnyRoute = RegisteredRouter["routeTree"],
TFrom extends RoutePaths<TRouteTree> | string = string,
TTo extends string = "",
TMaskFrom extends RoutePaths<TRouteTree> | string = TFrom,
TMaskTo extends string = "",
>({
variant,
testId,
linkProps,
children,
}: {
variant?: StyledLinkProps["variant"];
testId?: StyledLinkProps["testId"];
linkProps: UseLinkPropsOptions<TRouteTree, TFrom, TTo, TMaskFrom, TMaskTo>;
children?: ReactNode;
}) {
return (
<StyledLink variant={variant} testId={testId}>
<Link {...linkProps}>{children}</Link>
</StyledLink>
);
} I also tried this version by @SeanCassiere. This was interesting because my editor is happy but function MyLink<
TRouteTree extends AnyRoute = RegisteredRouter["routeTree"],
TFrom extends RoutePaths<TRouteTree> | string = string,
TTo extends string = "",
TMaskFrom extends RoutePaths<TRouteTree> | string = TFrom,
TMaskTo extends string = "",
>({
variant,
testId,
linkProps,
children,
}: {
variant?: StyledLinkProps["variant"];
testId?: StyledLinkProps["testId"];
linkProps: React.PropsWithoutRef<
LinkProps<TRouteTree, TFrom, TTo, TMaskFrom, TMaskTo> &
Omit<React.HTMLProps<'a'>, 'children' | 'preload'>
>;
children?: ReactNode;
}) {
return (
<StyledLink variant={variant} testId={testId}>
<Link {...linkProps}>{children}</Link>
</StyledLink>
);
} This previous version worked fine in function MyLink<
TRouteTree extends AnyRoute = RegisteredRouter["routeTree"],
TFrom extends RoutePaths<TRouteTree> | string = string,
TTo extends string = "",
TMaskFrom extends RoutePaths<TRouteTree> | string = TFrom,
TMaskTo extends string = "",
>({
variant,
testId,
linkProps,
children,
}: {
variant?: StyledLinkProps["variant"];
testId?: StyledLinkProps["testId"];
linkProps: LinkProps<TRouteTree, TFrom, TTo, TMaskFrom, TMaskTo> & React.RefAttributes<HTMLAnchorElement>;
children?: ReactNode;
}) {
return (
<StyledLink variant={variant} testId={testId}>
<Link {...linkProps}>{children}</Link>
</StyledLink>
);
}
@ziw 100% this. There is a clear need to create a custom wrapper for the provided |
Not sure if this helps, but there's a variation of this, that I'm using that works. const AppNavigationLink = <
TRouteTree extends AnyRoute = RegisteredRouter["routeTree"],
TFrom extends RoutePaths<TRouteTree> | string = string,
TTo extends string = "",
TMaskFrom extends RoutePaths<TRouteTree> | string = TFrom,
TMaskTo extends string = "",
>(props: {
name: string;
props: Omit<
React.PropsWithoutRef<
LinkProps<TRouteTree, TFrom, TTo, TMaskFrom, TMaskTo> &
Omit<React.ComponentPropsWithoutRef<"a">, "preload">
>,
"children" | "className" | "activeProps" | "inactiveProps"
>;
}) => {
const { name, props: linkProps } = props;
return (
<li>
<Link
className="..."
activeProps={{ className: "..." }}
inactiveProps={{ className: "..." }}
{...linkProps}
>
{name}
</Link>
</li>
);
}; |
We are working on this! From what I can gather, most of the questions on the Discord questions channel and here related to this topic, is around having a type which can be applied into a custom component's import { Link, type SomeNewLinkPropsType } from "@tanstack/react-router";
function CustomLink({ linkProps } : { linkProps: SomeNewLinkPropsType }) {
const { className, ...rest } = linkProps;
return <Link className={`foo-bar ${className}`} {...rest} />
} Once we figure out the correct story for the user-facing types for this, we'll make sure the documentation is updated to reflect the correct way forward. |
So I looked at I've tested with additional properties, searchParams validation etc, it all looks good. One issue is that Once that is merged, I think Only requirement is that Let me know if there's anything I'm missing |
also.. I was pleasantly surprised when I initially used and submit a PR which I believe resolves the props issue.. (but not the type error on |
It's probably not relevant anymore, but |
I am using
I think this is related to the same issue, I had following code which does not satisfy TS any longer: function TabbedNavigationItem<
TRouteTree extends AnyRoute = RegisteredRouter["routeTree"],
TFrom extends RoutePaths<TRouteTree> | string = string,
TTo extends string = "",
TMaskFrom extends RoutePaths<TRouteTree> | string = TFrom,
TMaskTo extends string = ""
>({
label,
activeHref,
linkProps
}: {
linkProps: LinkProps<TRouteTree, TFrom, TTo, TMaskFrom, TMaskTo> &
Omit<React.HTMLProps<"a">, "children" | "preload">
label: string
activeHref: LinkProps["to"]
}) { |
The weird TS issues I was having seems to be resolved in version // ...omitted
import type { LinkProps, RegisteredRouter, RoutePaths } from '@tanstack/react-router';
import { Link } from '@tanstack/react-router';
export type LinkButtonProps<
TRouter extends RegisteredRouter = RegisteredRouter,
TFrom extends RoutePaths<TRouter['routeTree']> | string = string,
TTo extends string = '',
TMaskFrom extends RoutePaths<TRouter['routeTree']> | string = TFrom,
TMaskTo extends string = '',
> = Pick<LinkProps<TRouter, TFrom, TTo, TMaskFrom, TMaskTo>, 'params' | 'search' | 'to'> &
StrictOmit<ButtonProps, 'onClick'>;
export const LinkButton = <
TRouter extends RegisteredRouter = RegisteredRouter,
TFrom extends RoutePaths<TRouter['routeTree']> | string = string,
TTo extends string = '',
TMaskFrom extends RoutePaths<TRouter['routeTree']> | string = TFrom,
TMaskTo extends string = '',
>({
params,
search,
to,
...props
}: LinkButtonProps<TRouter, TFrom, TTo, TMaskFrom, TMaskTo>) => {
return (
// @ts-expect-error Not sure what's causing this type error
<Link params={params} search={search} to={to}>
<Button {...props} />
</Link>
);
}; In practice: // No type error
<LinkButton params={{ productId: "123" }} to="/app/product/$productId" label="Go to Product" />
// Type error (expects params prop)
<LinkButton to="/app/product/$productId" label="Go to Product" /> |
Since creating a custom wrapper component for function StyledLink({ children, testId }: StyledLinkProps) {
const childrenWithClassName = Children.map(children, (child) =>
cloneElement(child, {
className: classes.styledLink,
"data-testid": testId,
}),
);
return <>{childrenWithClassName}</>;
} We use it like this: <StyledLink testId="myAnchor">
<Link
to={routeToSeppo}
params={seppoParams}
>
Seppo Taalasmaa
</Link>
</StyledLink> One downside with this approach is that we need to remember to manually wrap every |
@Jarzka did you try out |
didn't know that Here's what I do:
that's it 🤷 . Now I can do:
everything is styled like my normal |
@TkDodo how do you apply I want to be able to achieve something like this: import { ListItem } from 'my-design-system';
function NavListItem(props: ButtonProps & LinkProps) {
return <ListItem as={Link} {...props} activeProps={{ className: 'ListItem-active' }} />;
}
<NavListItem to="/my/route" params={{ foo: 'bar' }}>
My Link
</NavListItem> |
Yes, I am aware of
|
Haven't done this yet since I only want this passed for our sideBar navigation, where I pass it manually. But you should be able to do one more layer of nesting:
|
And what about |
The It's not a magic bullet type for use with |
Describe the bug
So I created my own Link component that in its basic form looks like this:
It works - I get a nice help from TypeScript autocompletes. It says - that I need "from" property in some situations.
posts/$id
posts/$id/A
posts/$id/B
In posts/$id.tsx - I display so I can see the subroutes.
If you run attached project like this - it works, MyLink component is fine.
(See here) https://stackblitz.com/edit/github-rb4ewj?file=src%2FMyLink.tsx
When I navigate to posts/$id - contains nothing... So - I was wondering - and added a new file posts/$id/index.tsx.
(I created a Fork from the previous stackblitz here: https://stackblitz.com/edit/github-rb4ewj-tg9tx2?file=src%2FMyLink.tsx)
And this is where things go wrong - Link now complains about something not satisfying something...
All I did - was just create an index.tsx under posts/$id folder.
maybe I am just doing this wrong? How else I can make a default subroute? I do need a common component that wraps children (in this case, it is /src/routes/posts/$id.tsx
Your Example Website or App
https://stackblitz.com/edit/github-rb4ewj?file=src%2FMyLink.tsx
Steps to Reproduce the Bug or Issue
Expected behavior
I expect Link to not complain about ToOptions passed to it.
Screenshots or Videos
No response
Platform
"@tanstack/react-router": "^1.19.0",
Additional context
No response
The text was updated successfully, but these errors were encountered: