router: ToOptions doesnt't satisfy props.

Describe the bug

  1. thing I need - a Component, that wraps <Link>, so I can add predefined styles to all of my links. To use “more advanced” paths with them, I found a Type - ToOptions (https://tanstack.com/router/v1/docs/framework/react/api/router/ToOptionsType), that I can pass to Link. So I created my own Link component that in its basic form looks like this:
import { Link, ToOptions } from '@tanstack/react-router';
import * as React from 'react';
type MyLinkProps = {
  toOptions: ToOptions;
};
const MyLink: React.FC<React.PropsWithChildren<MyLinkProps>> = ({
  children,
  toOptions,
}) => {
  return <Link {...toOptions}>{children}</Link>;
};
export default MyLink;

It works - I get a nice help from TypeScript autocompletes. It says - that I need “from” property in some situations.

  1. I want routes that looks like this: posts/$id posts/$id/A posts/$id/B

In posts/$id.tsx - I display <Outlet /> 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 - <Outlet /> 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…

Type '{ children: ReactNode; to: "/" | "/posts/$id" | "/posts/$id/A" | "/posts/$id/B" | "/posts/$id/"; hash?: true | Updater<string> | undefined; state?: true | NonNullableUpdater<HistoryState> | undefined; from?: RoutePathsAutoComplete<...> | undefined; search?: true | ... 1 more ... | undefined; params?: true | ... 1 mo...' 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: ReactNode; to: "/" | "/posts/$id" | "/posts/$id/A" | "/posts/$id/B" | "/posts/$id/"; hash?: true | Updater<string> | undefined; state?: true | NonNullableUpdater<HistoryState> | undefined; from?: RoutePathsAutoComplete<...> | undefined; search?: true | ... 1 more ... | undefined; params?: true | ... 1 mo...' 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; } & {})>'.(2322)

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

  1. Go here: https://stackblitz.com/edit/github-rb4ewj?file=src%2FMyLink.tsx
  2. Add index.tsx file under /src/routes/posts/$id/ folder (you might neeed to restart vite dev so it regenreates the content for the file)
  3. Open MyLink component and see the issue.

Expected behavior

I expect Link to not complain about ToOptions passed to it.

Screenshots or Videos

No response

Platform

Additional context

No response

About this issue

  • Original URL
  • State: open
  • Created 4 months ago
  • Reactions: 3
  • Comments: 18 (11 by maintainers)

Most upvoted comments

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

@ziw 100% this. There is a clear need to create a custom wrapper for the provided Link component and it becomes a big problem if the typings of the library do not remain stable.

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 props, to then later be applied on the TSR provided <Link> component.

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.

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 <Link> components in a type safe way. Even the customized solutions that work, only the to prop is typed but things like search and params do not get type safety that match to.

Adding

{...toOptions}
from={"/"}

satisfies props… But this will overwrite the "from" from the toOptions