-
Notifications
You must be signed in to change notification settings - Fork 26.8k
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
Statically Typed Links not working when passing href #47689
Comments
I can't reproduce this, there's no errors on my end. Can you upload your |
@DuCanhGH his tsconfig is present in the codesandbox:
|
Perhaps you can try something like this? import { Route } from "next";
import Link from "next/link";
import { Page } from "../types";
type Props = {
page: Page<unknown>;
hrefForPage<T extends string>(page: string): Route<T> | URL;
};
export function Pager(props: Props) {
// ...
} or you can assert the type to TS by using |
Doing that breaks another component called import Link from 'next/link';
import { Route } from 'next';
/**
* The BackButton component renders back button which is a
* <Link> under the hood.
*/
export function BackButton<T extends string>({ href }: Props<T>) {
return (
<Link href={href}>
Back
</Link>
);
} Gives me the following error:
|
@MrHus seems unrelated to each other, but a weird issue nonetheless as there's literally a test with the exact same component... Deleting |
The issue appears only when applying your fix, before that error does not appear. |
@MrHus I still see the issue without that change applied, perhaps there was something wrong with |
The
It does not fix the Btw running in the "codesandbox" the command: "npm run tsc" is what also gives the error. |
@MrHus I copied the code into a new Next project and the error still appears. It is probable that something changed between TS 4.8 and 5.0 that caused TS to infer the generic for |
I do also get an error when my Link is pointing to the root directory "/"
Go Back
</Link>">
To remove the error I have to either point to another route which is not the root one, or pass the pathname :
|
The issue also applies to the official Reproduction
Tried changes1. Typing of inner functionWhen I change line 10: - const redirectedPathName = (locale: string) => {
+ const redirectedPathName = <T extends string>(locale: string): Route<T> | URL => { I get Errors on line 11 and 14 where strings are returned, but the Link component href is fine:
2. Typing of Link componentWhen I change line 8 and line 24: - export default function LocaleSwitcher() {
+ export default function LocaleSwitcher<T extends string>() {
...
- <Link href={redirectedPathName(locale)}>{locale}</Link>
+ <Link<Route<T> | URL> href={redirectedPathName(locale)}>{locale}</Link> I get a similar error to the original one (shorted version copied from VSCode tooltip):
EDIT: If I remove the |
ERROR:
Code:
Lastly i try to found solution and come to consider:
|
You're not doing a nullcheck on the buttonGo entry. Which because of it being a optional entry results in the "string | undefined" type. |
In file next.config.js, deactivate typedRoutes: typedRoutes: false |
As the docs says (link), you'll have to either infer the correct link type, or cast it to In this example, you have interface CommonButtonProps {
buttonText: string;
buttonType?: string;
buttonGo?: UrlObject | string;
}
const CommonButton: FC<CommonButtonProps> = ({ buttonText, buttonType, buttonGo }) => {
return (
<div className="mt-8 text-center">
{buttonType === 'Link' ? (
<Link href={buttonGo} className=" bg-green-600 hover:bg-green-500 text-white py-4 px-6 rounded-lg uppercase">
{buttonText}
</Link>
) : (
<button className="bg-green-600 hover:bg-green-500 text-white py-4 px-6 rounded-lg uppercase">
{buttonText}
</button>
)}
</div>
);
}; You can either do interface CommonButtonProps<T extends string> {
buttonText: string;
buttonType?: string;
buttonGo?: UrlObject | Route<T>;
}
function CommonButton<T extends string>({ buttonText, buttonType, buttonGo }: CommonButtonProps<T>) {
return (
<div className="mt-8 text-center">
{buttonType === 'Link' ? (
<Link href={buttonGo} ... |
I get a TS error in a similar situation trying to write the following: app/presentation-list/page.tsx
Folder layout:
link.d.ts file shortened extract (multiple lines deleted) after npm run build
I wondered if the typing of
Renaming the Creating a
Is this expected behaviour at this point? (and is it expected to change as this becomes less experimental?) The typescript error message seems to suggest that these should be allowed as ' |
@shuding this type assertion currently fails with the
This is a fairly new issue, since a few releases I think. Maybe I should create an issue. |
A variation on this is what we ended up doing in our application. Here's our version, using the imported import Link, { LinkProps } from 'next/link';
type CardLinkProps<RouteInferred> = LinkProps<RouteInferred>;
function CardLink<Route>(props: CardLinkProps<Route>) {
return (
<div className="m-3 inline-block">
<Link href={props.href}>
{props.children}
</Link>
</div>
);
} |
Here's another version, in case you want to have a nested data structure containing multiplle import Link, { LinkProps } from 'next/link';
type Props<RouteInferred> = {
links: {
title: string;
href: LinkProps<RouteInferred>['href'];
}[];
};
export default function Links<Route>(props: Props<Route>) {
return (
<div>
{props.links.map((link) => {
return <Link href={link.href}>{link.title}</Link>;
})}
</div>
);
} Or, using import { Route } from 'next';
import Link from 'next/link';
type Props<RouteInferred extends string> = {
links: {
title: string;
href: Route<RouteInferred>;
}[];
};
export default function Links<Route extends string>(props: Props<Route>) {
return (
<div>
{props.links.map((link) => {
return <Link href={link.href}>{link.title}</Link>;
})}
</div>
);
} |
Hello @karlhorky
import { usePathname , useRouter } from "next/navigation";
const Comp = () => {
const router = useRouter();
const pathname = usePathname();
// Argument of type 'string' is not assignable to parameter of type 'RouteImpl<string>'.ts(2345)
router.push(pathname)
} Thank you! |
This was exactly what I needed — thx as always @shuding |
Not ideal but this fixes the linter router.push(url as Route) |
This looks like it should work but for some reason doesn't for me, getting type errors in CI Here's an alternative that works for me: import type { ComponentProps } from "react";
import type { Route } from "next";
import Link from "next/link";
type ExtendedLinkProps<R> = Omit<ComponentProps<typeof Link>, 'href'> & { href: Route<R> };
function ExtendedLink<R>(props: ExtendedLinkProps<R>) {
return (
<Link {...props} />
);
} |
This often indicates that you haven't built on CI - you need to run |
Works for me const PRIVACY_URL = `/profile/settings/${SETTINGS_URLS.PRIVACY}` as const;
...
<Header
backLinkUrl={section === null ? undefined : PRIVACY_URL}
/> import Link, { LinkProps } from 'next/link';
...
type HeaderProps<T> = {
backLinkUrl?: LinkProps<T>['href'];
};
export const Header = <T extends string>({
backLinkUrl = '/profile/settings',
}: HeaderProps<T>) => {
return (
<Link href={backLinkUrl}>
<Icon name="Back" />
</Link>
);
}; |
Oh, good call, that's probably it. Bit unfortunate though, having to run a several minutes build just to check types. I guess just a limitation of how Next does typed routes. (Other frameworks generate the route types as an actual file that you can commit.) |
Well in that scenario your types in SC can be out of date. While in this way it can't 😉. And regarding that "extra build" you can skip that by the use of caching like with the use of TurboRepo |
Verify canary release
Provide environment information
Operating System: Platform: darwin Arch: x64 Version: Darwin Kernel Version 21.6.0: Mon Dec 19 20:44:01 PST 2022; root:xnu-8020.240.18~2/RELEASE_X86_64 Binaries: Node: 18.15.0 npm: 9.5.0 Yarn: N/A pnpm: N/A Relevant packages: next: 13.2.5-canary.21 eslint-config-next: 13.2.4 react: 18.2.0 react-dom: 18.2.0
Which area(s) of Next.js are affected? (leave empty if unsure)
App directory (appDir: true), TypeScript
Link to the code that reproduces this issue
https://codesandbox.io/p/sandbox/angry-scott-twxtz4?file=%2F.next%2Ftypes%2Flink.d.ts&selection=%5B%7B%22endColumn%22%3A17%2C%22endLineNumber%22%3A32%2C%22startColumn%22%3A17%2C%22startLineNumber%22%3A32%7D%5D
To Reproduce
I've been trying server components with the new "app" directory with the
typedRoutes: true
config set tootrue
.It generates the following: routes definitions in the Next.js folder:
I get a TypeScript error in a component called
<Pager>
it creates a two buttonpagination component:
The idea behind the
hrefForPage
is that it is a callback function, which should return the proper url forthe next and previous
Link
, given thepage
number as astring
.This differs from the beta documentation example, as that example passes the
href
directly, with no function in between.Note: that I also have components which do pass the href like in the documentation, and this works like a charm.
TypeScript does not however agree that the
hrefForPage
returns the correct type:Describe the Bug
TypeScript gives an error whenever a callback function returns
Route<T> | URL
when passing the result of that callback function to thehref
prop of theLink
component.Expected Behavior
I expect that TypeScript thinks that the result of a function that returns
Route<T> | URL
should be valid.Which browser are you using? (if relevant)
No response
How are you deploying your application? (if relevant)
No response
The text was updated successfully, but these errors were encountered: