router icon indicating copy to clipboard operation
router copied to clipboard

Router searchParameter gets reset to default value (of zod schema) after navigations (and using retainSearchParams)

Open hakoomen opened this issue 1 year ago • 13 comments

Which project does this relate to?

Router

Describe the bug

Using retained searchParams, if we change the retained param x form a to b on some route (lets say route-a) and we navigate to route-b, which doesn't change param x, param x will revert back to its default value.

This is a regression because it works on using older versions.

Same code older versions: stackblitz Same code latest versions: stackblitz

Your Example Website or App

-see desc

Steps to Reproduce the Bug or Issue

  1. click on Home route (see Auth defaults to true)
  2. click on Settings route (see Auth remains true)
  3. click on About route (see now Auth is false)
  4. click on Home route: 4.a older version: Auth remains as false (which is expected) 4.b newer version: Auth changes back to true (which is unexpected)

Expected behavior

after chaning the retained searchParams, it should stay unchanged after navigations, and only change if explicitly said so.

  • some other weird thing here is that even without using this middle params stay on URL, kinda making retainSearchParams useless, unless im not grasping the use of it correctly.

Screenshots or Videos

No response

Platform

  • OS: [e.g. macOS, Windows, Linux]
  • Browser: [e.g. Chrome, Safari, Firefox]
  • Version: [e.g. 91.1]

Additional context

No response

hakoomen avatar Nov 24 '24 10:11 hakoomen

which version exactly introduced the regression?

schiller-manuel avatar Nov 24 '24 12:11 schiller-manuel

hi @schiller-manuel, its specifically PR #2747, works on @tanstack/react-router 1.81.4, doesnt on 1.81.5

FoHoOV avatar Nov 25 '24 06:11 FoHoOV

I'm seeing the same issue too

rminami avatar Nov 26 '24 05:11 rminami

same here, can confirm that 1.81.5 introduced this bug

ChristianK43 avatar Nov 28 '24 17:11 ChristianK43

@schiller-manuel can we fix this please, its really bugging me :(

FoHoOV avatar Jan 23 '25 14:01 FoHoOV

Seeing the same issue. Confirmed on my end this broke going from 1.81.4 to 1.81.5

OwenVey avatar Jan 23 '25 18:01 OwenVey

@schiller-manuel this bug destroys all navigations with default searchParams (overrides provided params with defaults). Can we make this a priority please? >_<

hakoomen avatar Mar 05 '25 09:03 hakoomen

@schiller-manuel I will second @hakoomen. I got bit by this bug for a while. this would be great to get fixed, it does break navigation for me when going between pages.

@schiller-manuel this bug destroys all navigations with default searchParams (overrides provided params with defaults). Can we make this a priority please? >_<

leftmostgeek avatar Mar 12 '25 22:03 leftmostgeek

for all link components or navigate calls we have to pass the current sp values like:

await router.navigate({
  to: "/",
  search: (prev) => prev
});

which is another paintpoint

FoHoOV avatar Apr 14 '25 13:04 FoHoOV

Can confirm the bug; The problem is that our internal validate middleware (that comes from validateSearchParams) runs first, so it sees an empty search and applies the schema, which adds default values. That happens before retainSearchParams runs).

It’s not as easy as just flipping the order though, as somethings have to run after validate. A bit tricky.

TkDodo avatar Apr 18 '25 11:04 TkDodo

The problem is that our internal validate middleware (that comes from validateSearchParams) runs first

Total newbie idea: Is it possible to expose the validateSearchParams middleware so that users have the option to control when validation happens? 🤔

Users could then work around this issue by placing the validation after the retainSearchParams.

e.g.

	validateSearch: undefined,
	search: {
		middlewares: [
			retainSearchParams(["foo"]),
			validateSearchParams(routeSchema),
		],
	},

It also gives users greater control for their more complex use cases.

jtannas avatar May 21 '25 22:05 jtannas

I dunno if it is related, and it might have to do with StrictMode, but when I open a url with search params, the first time I have access to them, the second time, they are undefined.

Is it also because of the validate?

humarkx avatar May 23 '25 23:05 humarkx

+1

juhyoung-swing avatar Jun 03 '25 14:06 juhyoung-swing

Anyone has a solution or workaround for this issue?

I tried to make my search params optional() instead of providing default() values. Beside the fact that now my application has to deal with the optional/undefined values (not a big deal here), activeProps from Link does not work really well.

In this example:

// I'd like to have 'id' as the default for 'orderBy':
const RouteSearchParams = z.object({
  orderBy: z.enum([ "id", "name" ]).default("id"),
});

// but with retainSearchParams I have to:
const RouteSearchParams = z.object({
  orderBy: z.enum([ "id", "name" ]).optional(),
});

When using a Link like this (no orderBy, my app code would handle undefined)

<Link to="..." search={{orderBy: undefined}}>...</Link>

the Link is always active, even when the current search is set to name.

On the other hand when using a Link like this:

<Link to="..." search={{orderBy: "id" }}>...</Link>

the link is (of course) not active when there is no orderBy search at all currently.

I tried with and without zod-adapter and tried zod v3 and v4.

I also tried to handle that somehow in an own middleware but unfortunately failed.

nilshartmann avatar Aug 12 '25 12:08 nilshartmann

since this is still unfixed i added a workaround in our app to be able to upgrade. if anyone is interested in it:

  • convert all navigate() calls and <Link>s to pass the previous search into the new search like navigate({..., search: prev => prev})
  • add a search middleware that strips out invalid search params and place it everywhere you've used retainSearchParams prior. sample:
return ({ search, next }) => {
  const result = next(search);

  const filteredResult = {};
  Object.keys(result).forEach(key => {
    if (validKeys.includes(key)) {
      filteredResult[key] =
        result[key];
    }
  });

  return filteredResult;
};

where validKeys are the keys of your search schema shape (e.g. Object.keys(yourSearchSchema.shape)

as a side note: make sure your schemas use the fallback pattern mentioned in the docs https://tanstack.com/router/latest/docs/framework/react/guide/search-params#zod

ChristianK43 avatar Aug 20 '25 22:08 ChristianK43