T
TanStack•16mo ago
metropolitan-bronze

Prevent same component from unmounting between routes

I have these three routes: * /search/ * /search/$query * /search/$query/$section All of these routes render my <SearchIndex /> component with a query and section props if those params are available. In our react-router@v3 setup the component stays mounted between routes if both routes render the same component, but I notice that TanStack Router re-mounts the component each time the matched route changes, which is causing weird state issues and preventing us from doing the transitions we want. Is it possible for the component to stay mounted in between route changes, if the component each route renders is the same but the props differ?
// search.index.tsx
export const Route = createFileRoute('/search/')({
component: () => <SearchIndex />,
})

// search.$query.tsx
export const Route = createFileRoute('/search/$query')({
component: SearchQueryRoute,
})
function SearchQueryRoute() {
const { query } = Route.useParams()
return <SearchIndex query={query} />
}

// search.$query.$section.tsx
export const Route = createFileRoute('/search/$query/$section')({
component: SearchQueryRoute,
})
function SearchQueryRoute() {
const { query, section } = Route.useParams()
return <SearchIndex query={query} section={section} />
}
// search.index.tsx
export const Route = createFileRoute('/search/')({
component: () => <SearchIndex />,
})

// search.$query.tsx
export const Route = createFileRoute('/search/$query')({
component: SearchQueryRoute,
})
function SearchQueryRoute() {
const { query } = Route.useParams()
return <SearchIndex query={query} />
}

// search.$query.$section.tsx
export const Route = createFileRoute('/search/$query/$section')({
component: SearchQueryRoute,
})
function SearchQueryRoute() {
const { query, section } = Route.useParams()
return <SearchIndex query={query} section={section} />
}
18 Replies
causal-orange
causal-orange•16mo ago
How could it stay mounted? those routes have nothing in common you could lift the search index component up into a /search route (notice the missing trailing slash, this is NOT the index route at /search/)
metropolitan-bronze
metropolitan-bronzeOP•16mo ago
Thanks for the super swift reply @Manuel Schiller! I'm not sure how it was achieved in react-router@v3, but it worked there 😅 Interesting idea, if I add it to the /search route, I wouldn't be able to use the TanStack APIs to get the route params right?
causal-orange
causal-orange•16mo ago
hm the problem is that /search does not know about its children path params so atleast in TS world they do not exist in that route
metropolitan-bronze
metropolitan-bronzeOP•16mo ago
It did work with:
export const Route = createFileRoute('/search')({
component: SearchRoute,
})

function SearchRoute() {
const { query, section } = useParams<any>({ strict: false })
return (
<SearchWrapper>
<SearchIndex query={query} section={section} />
</SearchWrapper>
)
}
export const Route = createFileRoute('/search')({
component: SearchRoute,
})

function SearchRoute() {
const { query, section } = useParams<any>({ strict: false })
return (
<SearchWrapper>
<SearchIndex query={query} section={section} />
</SearchWrapper>
)
}
But that's only because I still have the other route files
causal-orange
causal-orange•16mo ago
so you would need to use the useParams({strict: false}) way ah you have that already
metropolitan-bronze
metropolitan-bronzeOP•16mo ago
If I use a $ for _splat , might that work?
causal-orange
causal-orange•16mo ago
(about that any: don't do that.... the params should be inferred automatically. and when https://github.com/TanStack/router/pull/1664 lands, it will feel a lot nicer)
GitHub
fix(react-router): use mapped type instead of intersections for all...
This improves the ts performance significantly ofuseSearch and useParams when strict is false for merging of search and params Still need to make it default
metropolitan-bronze
metropolitan-bronzeOP•16mo ago
Not ideal as I wouldn't have my two named path params
causal-orange
causal-orange•16mo ago
ah, you only added those routes because you wanted to express "optional path params"?
metropolitan-bronze
metropolitan-bronzeOP•16mo ago
Yes, my SearchIndex component has three states: * /search - The index, showing an empty search bar * /search/$query - A search for $query was started * /search/$query/$section - A specific $section for $query was expanded (e.g. "album")
causal-orange
causal-orange•16mo ago
and it must be path params? I would use search params for this but yeah, that might work but you would have to split the splat part yourself
causal-orange
causal-orange•16mo ago
Routing Concepts | TanStack Router React Docs
TanStack Router supports a number of powerful routing concepts that allow you to build complex and dynamic routing systems with ease. The Root Route
metropolitan-bronze
metropolitan-bronzeOP•16mo ago
I'm afraid that we're already use this URL structure in our production app using react-router and will want to continue supporting this Agree with you that search params feel more on-point though
causal-orange
causal-orange•16mo ago
I see your approach (building multiple routes) for optional path params was suggested here as well: https://github.com/TanStack/router/discussions/146#discussioncomment-7984294
GitHub
Optional params? · TanStack router · Discussion #146
In react router v6 they are no longer supported. for example clients/:id? would match both /clients and /clients/1 Is there a way to achieve something like this in react location?
metropolitan-bronze
metropolitan-bronzeOP•16mo ago
As for dropping <any> on the useParams(), is there any best practice to get the param? This is what I came up with but it's very verbose:
const params = useParams({ strict: false })
let splat = '_splat' in params ? params._splat : ''
const params = useParams({ strict: false })
let splat = '_splat' in params ? params._splat : ''
No description
causal-orange
causal-orange•16mo ago
this should work:
const {_splat} = useParams({ strict: false, experimental_returnIntersection: true })
const {_splat} = useParams({ strict: false, experimental_returnIntersection: true })
but as stated above, as soon as this PR lands, this flag will be dropped and the return type will be an optional "intersection" of all path params
correct-apricot
correct-apricot•16mo ago
while moving to a shared layout is probably the better move, I'm not sure why a re-mount should occur if you render the same component. I would get it for:
component: () => <SearchQueryRoute />
component: () => <SearchQueryRoute />
because that would be a new component, but like this:
component: SearchQueryRoute
component: SearchQueryRoute
should mean the same component is rendered at the same position, which means react should be able to reconcile to the same instance and re-use it ...
causal-orange
causal-orange•16mo ago
sounds like hoping for an implementation detail?

Did you find this page helpful?