Skip to content

Declarative mask on search parameters applies previous route’s search state instead of the new one #5062

@LouisBouch

Description

@LouisBouch

Which project does this relate to?

Router

Describe the bug

When using a declarative mask on search parameters, the prev argument in the mask’s search function contains search values from the previously active route instead of the search parameters defined on the clicked Link.

More precisely, the search function runs 4 times upon clicking on the masked route's Link.

  • For the first two, prev contains search values from the previously active route.
  • For the next 2, it contains the search parameters defined on the currently clicked Link.

However, the resulting URL reflects the values from the first two calls (the stale route’s parameters), rather than the intended values from the clicked Link.

Mask

const homeMask = createRouteMask({
  routeTree,
  from: "/other",
  to: "/other",
  search: (prev) => {
   // The timestamp is used to emphasize the console output, it is otherwise not needed. 
    const timestamp = new Date();
    
    let val = prev.query + " " + timestamp.getMinutes() + "minutes" + " " + timestamp.getSeconds() +"seconds";
    console.log(val);

    return {
      ...prev,
      query: val,
      hasDiscount: undefined,
    };
  },
});

Links

<Link
  to="/profile"
  search={{
     query: "profile",
     hasDiscount: true,
  }}
>
  Prof
</Link>
<Link
  to="/other"
  search={{
    query: "other",
    hasDiscount: true,
  }}
>
  other
</link>

Type validation (same for both routes)

export const Route = createFileRoute("/profile")({
  component: RouteComponent,
  validateSearch: ({ query, hasDiscount }): ret => {
    return {
      query: query as string | undefined,
      hasDiscount: hasDiscount as boolean | undefined,
    };
  },
});

Your Example Website or App

https://stackblitz.com/edit/vitejs-vite-4dj9qsmg?file=src%2FApp.tsx

Steps to Reproduce the Bug or Issue

  1. After opening the link, open the preview in a new tab using the top right button (Cannot see the url otherwise).
  2. Click on the Profile Link
  3. Open the console and clear it
  4. Click on the Other Link.
  5. Observe:
    • The url contains .../other?query=profile...
    • The console logs 4 outputs, two from the mask running on the profile route and 2 outputs from the mask running on the other route. All 4 outputs ran at the same time.

Expected behavior

The url should instead contain: .../other?query=other...

Instead of using the previous route's search parameters, the mask should use the parameters from the activated Link.

Screenshots or Videos

Console logs after clicking on "profile" and after clicking on "other".

Image

Url after clicking on "profile". (Normal, no masking)

Image

Url after clicking on "other". (Masking, but with value of profile)

Image

Platform

  • Router Version: 1.131.31
  • Browser: Chrome, Firefox
  • React Version: 19.1.1
  • Bundler: vite
  • Bundler Version: 7.1.3

Additional context

The documentation for masking only includes a full example for masking using path parameters. The documentation also does not mention this specific use case. That being said, I was able to reproduce the desired behavior using imperative masking, which makes me believe this behavior is not desired.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions