T
TanStack2mo ago
optimistic-gold

Pathless Layout search params.

Are pathless layouts allowed to define/set search params for their child routes? I currently have a setup where the pathless layout contains the filter controls for a gallery, which is supposed to store the values in search params. however the search params keep flashing. pathless contains the validateSearch and search middleware, while the child tries to read search params from loaderdeps. Not sure if a bug, bad configuration or just not supported currently.
12 Replies
optimistic-gold
optimistic-goldOP2mo ago
i see this in the docs Search Params are inherited from Parent Routes The search parameters and types of parents are merged as you go down the route tree, so child routes also have access to their parent's search params:
shop.products.tsx
const productSearchSchema = z.object({
page: z.number().catch(1),
filter: z.string().catch(''),
sort: z.enum(['newest', 'oldest', 'price']).catch('newest'),
})

type ProductSearch = z.infer<typeof productSearchSchema>

export const Route = createFileRoute('/shop/products')({
validateSearch: productSearchSchema,
})
shop.products.$productId.tsx
export const Route = createFileRoute('/shop/products/$productId')({
beforeLoad: ({ search }) => {
search
// ^? ProductSearch :white_check_mark:
},
})
shop.products.tsx
const productSearchSchema = z.object({
page: z.number().catch(1),
filter: z.string().catch(''),
sort: z.enum(['newest', 'oldest', 'price']).catch('newest'),
})

type ProductSearch = z.infer<typeof productSearchSchema>

export const Route = createFileRoute('/shop/products')({
validateSearch: productSearchSchema,
})
shop.products.$productId.tsx
export const Route = createFileRoute('/shop/products/$productId')({
beforeLoad: ({ search }) => {
search
// ^? ProductSearch :white_check_mark:
},
})
but the behaviour doesnt seem to be replicated with a pathless layout as the parent
other-emerald
other-emerald2mo ago
can you please create a minimal complete reproducer example?
optimistic-gold
optimistic-goldOP2mo ago
Yes will do https://stackblitz.com/edit/tanstack-router-zswsuhha?file=src%2Froutes%2F_controls.tsx Here is a reproduction. If you go to the Gallery page you will see that link One and Two work, with <Link> and useNavigate({ from: '/gallery', }); both using to: "." however link Three that uses Route.useNavigate() removes you off of the gallery page and goes back to the index Not sure if this is intended but i'd expect the behaviour to keep the user on the same page with Route.useNavigate() and to="." Errors are actually showing in the stackblitz IDE that werent showing in cursor with the same tsconfig
other-emerald
other-emerald2mo ago
cc @Nico Lynzaad
wise-white
wise-white2mo ago
Literally running into this right now as well. Any updates on this? I have an pathless _auth route that my "entire" application is behind. In the _auth / route.tsx of my app there is a datepicker that you can choose a month for data which the rest of the application uses that month for in the components' respective queries. When in the datepicker I am trying to call
const navigate = useNavigate({ from: '/_auth' });

const updateActiveMonth = async (newMonth: dayjs.Dayjs) => {
const formatted = newMonth.format('YYYYMM');
await navigate({
search: (prev) => ({
activeReportDate: formatted,
...prev,
}),
replace: true,
});
};
const navigate = useNavigate({ from: '/_auth' });

const updateActiveMonth = async (newMonth: dayjs.Dayjs) => {
const formatted = newMonth.format('YYYYMM');
await navigate({
search: (prev) => ({
activeReportDate: formatted,
...prev,
}),
replace: true,
});
};
But technically I cannot call const navigate = useNavigate({ from: '/_auth' }); because type '"/_auth"' is not assignable to type 'FromPathOption<RouterCore<Route<any, "/", "/" since once authd the user is just on one of three auth routes /a /b /c and I can't hard code one of those routes because it's not clear what route the user is on. But for some reason this works (functionally)
const navigate = useNavigate({ from: Route.fullPath });

const updateActiveMonth = async (newMonth: dayjs.Dayjs) => {
const formatted = newMonth.format('YYYYMM');
await navigate({
search: (prev) => ({
...prev,
activeReportDate: formatted,
}),
replace: true,
});
};
const navigate = useNavigate({ from: Route.fullPath });

const updateActiveMonth = async (newMonth: dayjs.Dayjs) => {
const formatted = newMonth.format('YYYYMM');
await navigate({
search: (prev) => ({
...prev,
activeReportDate: formatted,
}),
replace: true,
});
};
but I still get ts errors on the search: (prev) ... line and on the useNavigate that Route.fullPath shows as "" rightfully so since the auth route is pathless. not really sure how to handle this for what its worth I'm seeing the same error in your search: (prev) on my end
like-gold
like-gold2mo ago
Thanks for the replication. This is supposed to work fine, but I'm guessing it's something specific to the pathless layout. I will investigate and get back to you.
optimistic-gold
optimistic-goldOP2mo ago
Sounds good! thanks for checking it out.
wise-white
wise-white2mo ago
Wyatt Protzman
StackBlitz
Pathless route search params - StackBlitz
Run official live example code for Router Basic File Based, created by Tanstack on StackBlitz
wise-white
wise-white2mo ago
To be fair, maybe I'm misunderstanding a thing or two thus not explaining it properly, but in my datepicker where I am trying to set the search params I'm getting ts errors
The expected type comes from property 'search' which is declared here on type 'NavigateOptions<RouterCore<Route<Register, any, "/", "/", string, "__root__", undefined, {}, {}, AnyContext, AnyContext, {}, undefined, RootRouteChildren, FileRouteTypes, unknown, unknown, undefined>, "never", false, RouterHistory, Record<...>>, string, string | undefined, string, "">'
The expected type comes from property 'search' which is declared here on type 'NavigateOptions<RouterCore<Route<Register, any, "/", "/", string, "__root__", undefined, {}, {}, AnyContext, AnyContext, {}, undefined, RootRouteChildren, FileRouteTypes, unknown, unknown, undefined>, "never", false, RouterHistory, Record<...>>, string, string | undefined, string, "">'
also fwiw this works functionally, just seems like something is set somewhere incorrectly causing the ts error
like-gold
like-gold2mo ago
okay so 2 things here, I will address the type error first and then the navigation issue as the one is common in both cases and are completely different issues: 1) types: when running locally the types are pulling through fine on @fatherofkook 's reproducer although this is erroring on stackblitz. @fatherofkook could you please confirm this is the case for you as well? @zander the types in your case suffers the same issue as @fatherofkook 's example. However its complicated with the use of zod's fallback and enums within the fallback that makes it difficult for typescript to infer it. doing the following resolved the issue locally:
onst tagOptions = ["one", "two", "three"] as const;

const searchSchema = z.object({
tag: fallback(z.enum(tagOptions), defaultSearchValues.tag).default(
defaultSearchValues.tag,
),
});
onst tagOptions = ["one", "two", "three"] as const;

const searchSchema = z.object({
tag: fallback(z.enum(tagOptions), defaultSearchValues.tag).default(
defaultSearchValues.tag,
),
});
2) @zander as for your navigation issue: Relative navigation is always relative to the from. with useNavigate and Link this from, if not defined, is assumed to be the current active path and hence navigation is happening from: '/_control/gallery' since this route is active. However when using the method available on your route, i.e. Route.useNavigate this from is predefined to be the route this is called on. In this case being a pathless layout route and since a pathless layout route has no path this is then moved back to its parent, in this case "/" so in your code it is working as expected reloading the "/" route. I will add some additional context to the docs regarding relative navigation and the impact of the predefined from when using the methods on the Route. @fatherofkook I noticed in your example you where using useSearch({from: Route.id}), the above holds true for Route.useSearch as well so using that you would not need to define the from. https://tanstack.com/router/latest/docs/framework/react/guide/navigation#special-relative-paths--and-
wise-white
wise-white2mo ago
Hey thanks for the response. I've stepped away but I'll look over this later
optimistic-gold
optimistic-goldOP2mo ago
great thanks for the insights, will be sure to remember that a from or to is required in this situation. I was also able to solve the types issue by switching to zod4 and removing the use of the zodValidator fallback. Definitely would be a great thing to add a little extra clarity on in the docs. Appreciate you looking into it!

Did you find this page helpful?