Skip to content
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 etc. types unusable? #1194

Open
jaens opened this issue Feb 17, 2024 · 11 comments
Open

ToOptions etc. types unusable? #1194

jaens opened this issue Feb 17, 2024 · 11 comments

Comments

@jaens
Copy link
Contributor

jaens commented Feb 17, 2024

Describe the bug

It's not possible to use the ToOptions, UseLinkPropsOptions etc. types to store a link in a variable. It seems to always result in a type error. This is in the "basic" example.

(passing the exact same object to eg. useLinkProps() works fine)

I'm not sure what the type of a link is supposed to be, as I can't find any other possibilities anywhere in the documentation? Links do need to be occasionally stored in variables...

(note that using as const to not have a type at all is not a solution, in this simple example it would be, but practically this type also needs to occur in parameters or props, which always need an explicit type.)

image

Your Example Website or App

CodeSandbox example (based on "basic")

(edited, initial link was broken due to CodeSandbox bug...)

Steps to Reproduce the Bug or Issue

Check the Codesandbox.

Expected behavior

There's some type I can use to store links that I can then pass to eg. useNavigate() or useLinkProps()

Screenshots or Videos

No response

Platform

all

Additional context

No response

@schiller-manuel
Copy link
Contributor

schiller-manuel commented Feb 17, 2024

ToOptions rely on TS inferring types in e.g. useNavigate, so you can't use them as stand-alone types.
If you really need this kind of flexibility (and want give up on a lot of type-safety e.g. for search/path params) you can store the to prop as a string and pass it in:

let to = '/';
useLinkProps({to});

@jaens
Copy link
Contributor Author

jaens commented Feb 17, 2024

I'm sorry, but that's not really a solution? I don't think this should be closed?

Are you saying there's no way to store links separately in a type safe way? That seems like... bad.

Based on my experience, this should definitely be possible.

A simple solution would be to have a link() function like in the CodeSandbox that just returns the link as is, but would infer types. It's the standard TypeScript pattern of using an identity-function for type inference.

The same pattern is used in eg. Tanstack Query for queryOptions().

I'm just not sure what the type of such a link-function would be, given the visible type declaration in the public API is basically unapproachable without documentation:

export type UseLinkPropsOptions<TRouteTree extends AnyRoute = RegisteredRouter['routeTree'], TFrom extends RoutePaths<TRouteTree> | string = string, TTo extends string = '', TMaskFrom extends RoutePaths<TRouteTree> | string = TFrom, TMaskTo extends string = ''> = .....

@schiller-manuel
Copy link
Contributor

What is "a link" exactly that you want to store in a "type safe way"? just the to part?

@jaens
Copy link
Contributor Author

jaens commented Feb 17, 2024

Everything that's in ToOptions, including parameters. Anything that can be passed to both useNavigate() and useLinkProps().

The example does demonstrate that at least UseLinkPropsOptions is usable as a stand-alone type and works when passed to useLinkProps(), the only issue is making an object literal conform to that type, which is merely some sort of problem with inference...

@jaens
Copy link
Contributor Author

jaens commented Feb 17, 2024

I would actually be happy with just a single function that can make a literal turn into any one (pick what you like best) of the *Options types, since that's where the main benefit of type safety lies.

Anything else can be achieved by casting between the *Options types and does not really sacrifice the main benefit of type safety, which is strongly typing the path and params.

@schiller-manuel
Copy link
Contributor

schiller-manuel commented Feb 17, 2024

Even if the type can be inferred by such a helper function, how would you pass those links using props etc?
You would have to spell out the types there again.
Can you give some concrete examples?

function A() {

    const myLink = linkHelper({to: '/foo/$fooId', params: { fooId: 'bar123'}});
    // what should the type of myLink now be?

   return <B link={myLink} />

}

interface BProps {
    link: ??? // what to put here?
}

function B({link} : BProps) {
   /// ...
}

@jaens
Copy link
Contributor Author

jaens commented Feb 17, 2024

Clearly, eg. ToOptions should theoretically be passable to both useNavigate and useLinkProps, since both functions work with anything that practically conforms to that type. Any other behaviour would be a bug in the types.

The answer then, of course, is link: ToOptions.

If that's somehow too complicated to fix, UseLinkPropsOptions would also be usable. It already works as a type in the CodeSandbox above.

@jaens
Copy link
Contributor Author

jaens commented Feb 17, 2024

As a practical use case, a very simple and common example would be eg. in a design system, where you would have a component wrapping a link:

function MyLinkLike({ link, renderAsButton, children } : { link: ???, ... }) {
   const navigate = useNavigate();
   return renderAsButton ? 
		<Button onPress={() => navigate(link)} /> :
		<SomeOtherComponent><Link {...link}>{children}</Link></SomeOtherComponent>;
}

The example code is obviously simplified and bad from various perspectives, but it demonstrates the basic issue.

@jaens
Copy link
Contributor Author

jaens commented Feb 21, 2024

@schiller-manuel I believe this is still a valid issue/problem (so this shouldn't be closed?)
Design systems often use generic link representations.

Here's my current "implementation" that "works":

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 UseLinkPropsOptions;
}

const _testLink: UseLinkPropsOptions = link({ to: "/" });

I can contribute a solution if there is consensus on how to move forward.

@tatwater
Copy link

In my case, I have a reusable component that, once the user is done with it, needs to be able to route different places based on where it started. I pass a prop I call closeTo into this component, and in its submit function it calls navigate({ to: props.closeTo }). I want to make sure that as more people work on this project with me, they get type-safe suggestions and warnings to ensure they pass a path into my component's closeTo prop that's a valid route in the routeTree. Having a quick and easy single exposed type that's even as simple as a union of all paths in the route tree would be awesome! Though I totally understand folks who would love type-safe params etc as well.

@CamParry
Copy link

I had the same issue and after a bit of playing around I found this works:

import { RouterProvider, createRouter, Link, ToOptions } from '@tanstack/react-router';

import { routeTree } from './routeTree.gen';

export const router = createRouter({
	defaultPreload: 'intent',
	routeTree
});

const MyLink = ({ link }: { link: ToOptions<typeof router> }) => {
   return <Link {...link}>Testing</Link>
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants