T
TanStack3mo ago
quickest-silver

Query State issues

Error: "Maximum update depth exceeded" when clicking the Select dropdown in TanStack Router. Works fine in React Router using nuqs. How to properly update search params without infinite re-renders?
// Route component
const searchSchema = z.object({
joined: z.optional(z.boolean()),
past: z.optional(z.enum(["all", "only", "exclude"])),
});

export const Route = createFileRoute({
validateSearch: (search) => searchSchema.parse(search),
component: RouteComponent,
});

function RouteComponent() {
const navigate = Route.useNavigate();
const search = Route.useSearch();

const updateFilters = useCallback(
(updates: { joined?: boolean; past?: string }) => {
navigate({
to: ".",
search: { ...search, ...updates },
});
},
[navigate, search],
);

return <MyTable updateFilters={updateFilters} />;
}

// Component that triggers the error
function MyTable({ updateFilters }) {
return (
<Select
value={search.joined ? "joined" : "all"}
onValueChange={(value) =>
updateFilters({ joined: value === "joined" })
}
>
{/* options */}
</Select>
...
);
}
// Route component
const searchSchema = z.object({
joined: z.optional(z.boolean()),
past: z.optional(z.enum(["all", "only", "exclude"])),
});

export const Route = createFileRoute({
validateSearch: (search) => searchSchema.parse(search),
component: RouteComponent,
});

function RouteComponent() {
const navigate = Route.useNavigate();
const search = Route.useSearch();

const updateFilters = useCallback(
(updates: { joined?: boolean; past?: string }) => {
navigate({
to: ".",
search: { ...search, ...updates },
});
},
[navigate, search],
);

return <MyTable updateFilters={updateFilters} />;
}

// Component that triggers the error
function MyTable({ updateFilters }) {
return (
<Select
value={search.joined ? "joined" : "all"}
onValueChange={(value) =>
updateFilters({ joined: value === "joined" })
}
>
{/* options */}
</Select>
...
);
}
3 Replies
conventional-tan
conventional-tan3mo ago
can you turn this into a complete example please, e.g. by forking one of the existing router examples on stackblitz?
useful-bronze
useful-bronze3mo ago
If it's the Select from shadcn / radix, I think it calls onValueChange on mount. I'm not entirely sure, but that might be your issue. If that's correct, then you might consider using the functional version of the search param in navigate to avoid re-creating your updateFilters function on every render:
const updateFilters = useCallback(
(updates: { joined?: boolean; past?: string }) => {
navigate({
to: ".",
search: (prev) => { ...prev, ...updates },
});
},
[navigate],
);
const updateFilters = useCallback(
(updates: { joined?: boolean; past?: string }) => {
navigate({
to: ".",
search: (prev) => { ...prev, ...updates },
});
},
[navigate],
);
And then wrap your MyTable in memo
quickest-silver
quickest-silverOP3mo ago
ah that must be why i couldn't reproduce this issue using the html select on stackblitz... let me try that

Did you find this page helpful?