TanStackT
TanStack13mo ago
5 replies
brilliant-lime

useMatchRoute but don't re-render on every state change

One thing (among many) that is awesome about tanstack/router is that most hooks can be used in a way that minimizes re-renders. For example:
const pathname = useLocation({ select: l => l.pathname }) // only re-renders when pathname changes
const foo = useSearch({ select: s => s.foo }) // only re-renders when foo changes
const router = useRouter() // never re-renders


But there is one pattern that I'm not really sure how to do without re-rendering on almost every router state change:
const match = useMatchRoute()
const isMatch = match({ to: '/a' }) || match({ to: '/b', fuzzy: true })


It seems to me that
useMatchRoute
must always re-render so that
match
gets re-executed (and it doesn't know what
match
is going to be called with).

match
is great because it's way more type-safe than (for example) just doing string comparisons with
pathname

const pathname = useLocation({ select: l => l.pathname })
const isMatch = pathname === '/a' || pathname.startsWith('/b')

Or even better
const isMatch = useLocation({
  select: l => l.pathname === '/a' || l.pathname.startsWith('/b')
})


This last example with
useLocation
causes way fewer re-renders than the equivalent with
useMatchRoute
, but it's not as typesafe.

How would you compute
isMatch
in a way that is both type-safe and minimizes re-renders?

There is something kind of close with
useMatch
but not quite the same
useMatch({ from: '/a', shouldThrow: false, select: Boolean })

But it's missing a couple things:
-
fuzzy:true
is pretty handy, if I want to match
/foo/a
and
/foo/b
but there is no
route
at /foo,
fuzzy:true
can do it, but
useMatch
cannot
- the ability to specify more than just
to
(like search, params, ...)
Was this page helpful?