T
TanStack4w ago
conscious-sapphire

Search-Typing issue

Hey 👋🏽 . I love tanstack router. Does somebody know why following works:
import { createFileRoute } from "@tanstack/react-router";
import { z } from "zod";
import { Login } from "shared-features/login";

export const Route = createFileRoute("/login")({
validateSearch: z.object({
redirect: z.string().catch("/app"),
}),
component: LoginComponent,
});

function LoginComponent() {
const navigate = Route.useNavigate();
const redirect = Route.useSearch({ select: (search) => search.redirect });

const handleRedirect = () => {
navigate({
to: redirect,
replace: true,
});
};

return (
<Login title="Hello" onLogin={handleRedirect}/>
);
}
import { createFileRoute } from "@tanstack/react-router";
import { z } from "zod";
import { Login } from "shared-features/login";

export const Route = createFileRoute("/login")({
validateSearch: z.object({
redirect: z.string().catch("/app"),
}),
component: LoginComponent,
});

function LoginComponent() {
const navigate = Route.useNavigate();
const redirect = Route.useSearch({ select: (search) => search.redirect });

const handleRedirect = () => {
navigate({
to: redirect,
replace: true,
});
};

return (
<Login title="Hello" onLogin={handleRedirect}/>
);
}
but following not:
import { createFileRoute } from "@tanstack/react-router";
import { Login } from "shared-features/login";

type LoginSearch = {
redirect: string;
};

export const Route = createFileRoute("/login")({
validateSearch: (search): LoginSearch => {
return {
redirect: (search.redirect as string) || "/app",
};
},
component: LoginComponent,
});

function LoginComponent() {
const navigate = Route.useNavigate();
const redirect = Route.useSearch({ select: (search) => search.redirect });

const handleRedirect = () => {
navigate({
to: redirect,
replace: true,
});
};

return (
<Login title="Hello" onLogin={handleRedirect}/>
);
}
import { createFileRoute } from "@tanstack/react-router";
import { Login } from "shared-features/login";

type LoginSearch = {
redirect: string;
};

export const Route = createFileRoute("/login")({
validateSearch: (search): LoginSearch => {
return {
redirect: (search.redirect as string) || "/app",
};
},
component: LoginComponent,
});

function LoginComponent() {
const navigate = Route.useNavigate();
const redirect = Route.useSearch({ select: (search) => search.redirect });

const handleRedirect = () => {
navigate({
to: redirect,
replace: true,
});
};

return (
<Login title="Hello" onLogin={handleRedirect}/>
);
}
The only difference is that in first version I use zod to validate the search, and in the second I do it myself.
7 Replies
conscious-sapphire
conscious-sapphireOP4w ago
For the second version I get:
Argument of type '{ to: string; replace: true; }' is not assignable to parameter of type 'NavigateOptions<RouterCore<Route<any, "/", "/", string, "__root__", undefined, {}, { queryClient: QueryClient; }, AnyContext, AnyContext, {}, undefined, RootRouteChildren, FileRouteTypes>, "never", false, RouterHistory, Record<...>>, "/login", string, "/login", "">'.
Property 'search' is missing in type '{ to: string; replace: true; }' but required in type 'MakeRequiredSearchParams<RouterCore<Route<any, "/", "/", string, "__root__", undefined, {}, { queryClient: QueryClient; }, AnyContext, AnyContext, {}, undefined, RootRouteChildren, FileRouteTypes>, "never", false, RouterHistory, Record<...>>, "/login", string>'.ts(2345)
link.d.ts(140, 5): 'search' is declared here.
Argument of type '{ to: string; replace: true; }' is not assignable to parameter of type 'NavigateOptions<RouterCore<Route<any, "/", "/", string, "__root__", undefined, {}, { queryClient: QueryClient; }, AnyContext, AnyContext, {}, undefined, RootRouteChildren, FileRouteTypes>, "never", false, RouterHistory, Record<...>>, "/login", string, "/login", "">'.
Property 'search' is missing in type '{ to: string; replace: true; }' but required in type 'MakeRequiredSearchParams<RouterCore<Route<any, "/", "/", string, "__root__", undefined, {}, { queryClient: QueryClient; }, AnyContext, AnyContext, {}, undefined, RootRouteChildren, FileRouteTypes>, "never", false, RouterHistory, Record<...>>, "/login", string>'.ts(2345)
link.d.ts(140, 5): 'search' is declared here.
Thanks
ugly-tan
ugly-tan4w ago
I think you would need to manually specify the input type of the search for the second one. it can be inferred from the zod Schema automatically.
ugly-tan
ugly-tan4w ago
RouteOptions type | TanStack Router React Docs
The RouteOptions type is used to describe the options that can be used when creating a route. RouteOptions properties The RouteOptions type accepts an object with the following properties: getParentRo...
conscious-sapphire
conscious-sapphireOP4w ago
You mean this?
validateSearch: (search: LoginSearch): LoginSearch => {
return {
redirect: search.redirect || "/app",
};
}
validateSearch: (search: LoginSearch): LoginSearch => {
return {
redirect: search.redirect || "/app",
};
}
I typed it already in my example, didn't I? If I hover over the redirect-var in the LoginComponent, I get const redirect: string, so it should be typed correctly. And the error message seems not related to the search-typing. thanks
ugly-tan
ugly-tan4w ago
then please provide a complete minimal example
equal-aqua
equal-aqua4w ago
You need to add SearchSchemaInput and mark the redirect property as optional:
validateSearch: (search: Partial<LoginSearch> & SearchSchemaInput)
: LoginSearch => { /* ... */ }
validateSearch: (search: Partial<LoginSearch> & SearchSchemaInput)
: LoginSearch => { /* ... */ }
conscious-sapphire
conscious-sapphireOP4w ago
Thanks @shelly! @Manuel Schiller The only thing I had to change in my 2nd example was making the redirect-prop optional.
type LoginSearch = {
redirect?: string;
};

// ...
validateSearch: (search: LoginSearch): LoginSearch => {
return {
redirect: search.redirect ?? "/app",
};
},
// ...
type LoginSearch = {
redirect?: string;
};

// ...
validateSearch: (search: LoginSearch): LoginSearch => {
return {
redirect: search.redirect ?? "/app",
};
},
// ...

Did you find this page helpful?