Hooks proposal
I have written 2 hooks that we've been finding pretty useful at work, I was wondering whether they might be of interest to the community. They're fully type-safe like native tanstack/router hooks (except for route masking because we don't use that, but that's seems doable if needed).
The first is
useNavigatePreload
, it works like useNavigate
, but in 2 steps, first to preload, then to actually navigate.
Here, we need to navigate programmatically because it needs to happen after a "code event" (a mutation here), and not a "user action" (where we would use a regular link). But we still want some preloading. So the preload()
call will do a router.loadRouteChunk
under the hood (not actually call the loaders, because the mutation is likely to change things in the destination route). Having navigate
tied to preload
ensures we preload the correct route.
The second is useSearchState
. It's based on useSearch
but behaves like a useState
.
This hook makes it very easy to manipulate search params in a way that is familiar to react devs. It also batches calls to the setState into a single navigate
call to minimize work by the router.
I can provide the actual implementation if someone wants either of those. Or maybe seeing the idea is helpful enough to someone.31 Replies
dependent-tan•4mo ago
about
useNavigatePreload
, why do you need this? navigate would also cause the route chunk to be loaded if not done so yet
useSearchState might be interesting, yes. i'll paste a version that @TkDodo 🔮 once wroteextended-salmonOP•4mo ago
that's because of situations like in the example: we know where we want to navigate before we actually want to navigate. So that gives us an opportunity to preload the route
dependent-tan•4mo ago
yeah but still, what does this actually do mean in terms of UX/UI?
it just means you dont trigger the pendingComponent since you are preloading
but it will not be faster
extended-salmonOP•4mo ago
it just means we're loading the JS chunks while the mutation is ongoing. Yeah it's not much, but it's so easy to use that we might as well.
dependent-tan•4mo ago
ah i see, you parallelize route chunk loading with mutation
extended-salmonOP•4mo ago
exactly
dependent-tan•4mo ago
maybe a bit too niche for a core hook
we should do it shadcn style, copy the hook in your project if you want it
whereas the useSearchState is more likely to be used i think
but still, there might be different tastes
this is why we held off with those higher level hooks for now
extended-salmonOP•4mo ago
TkDodo's useSearchState is simpler than mine! I might have gotten a little lost in the types, but I also have batching which I think is pretty nice for a hook like this (since a useState would also batch subsequent calls)
extended-salmonOP•4mo ago
yeah I definitely get it, I just though I might add my grain of salt to the conversation
dependent-tan•4mo ago
absolutely appreciated
thats why I think a different distribution mechanism than putting it in a library is probably best here
extended-salmonOP•4mo ago
ah ah I don't like the shadcn style distribution, I always get FOMO that I don't have the latest version of whatever stuff I copied. But ppl seem to enjoy it.
dependent-tan•4mo ago
lol
but you already see there are many ways to rome here for e.g. useSearchState
extended-salmonOP•4mo ago
yeah but mine's better (i'm just trolling)
thanks for the chat
dependent-tan•4mo ago
i would really like some user contributed high level hook collection somewhere (outside of discord)
extended-salmonOP•4mo ago
since I wrote those hooks, I think they broke at least 2 times on minor releases of tanstack/router, maybe when the lib reaches a slightly more stable state then it might be more reasonnable to have ppl copy/paste some high level hooks
dependent-tan•4mo ago
how did they break?
types?
or runtime
extended-salmonOP•4mo ago
yeah types only
dependent-tan•4mo ago
ok, thats kinda expected when reaching deep into the router types
no guarantuees there
extended-salmonOP•4mo ago
I'm not blaming the release strategy, I'm ok w/ it, but if we have this kind of hook copy/pasted in many codebases, we can't "remote fix" them
dependent-tan•3mo ago
sure
slippery slope
coming back to this
why do we need the "batching" part?
extended-salmonOP•3mo ago
Sorry I never saw those last messages. The batching is so it behaves like a react
useState
:
In this example, if setFoo
and setBar
come from useState
, this will only cause a single render. In this same vein, if they come from useSearchState
, they should only cause 1 navigation.dependent-tan•3mo ago
what happens without the batching and multiple set calls from useSearchState ?
multiple renders?
we should get this into the main repo
vicious-gold•3mo ago
Hi!
Super cool topic, was looking for the same thing.
I was tired of writing the same setter with (prev) => {...prev, etc.} all the time.
So +1 on having this in the lib in any way 🙂
In the meantime, I was wondering:
- Would you be able to share a version of the pasted useSearchState that compiles, with all imports & all (the one from tkDodo ?)
- On bostonsheraff's version, for my information, how come you need the binding and all? From your version I was able to get it to work with only this:
And adding my two cents, here is a helper returning a function to get the setter from the key, using currying:
Any comments or suggestions welcomed!
extended-salmonOP•3mo ago
I'm using bind to avoid recreating function scopes on every re-render. This is a minor optimisation, but since this kind of hook usually goes in components that re-render pretty often, it felt like a free win
I don't remember well, I should test it to give an answer. But at least 2 navigations (2 history replaces, or if you push, 2 actual history entries), I don't remember if it does re-render twice or not.
vicious-gold•3mo ago
Hi !
Adding my 2 cents again because I've been playing with the matter recently and found the topic to be fun:
I've come up with a new helper that generates the various (independent) setters for the search params, from the route, and properly typed.
It allows to consume them like this:
This way I can stay close to the way the framework is expressed but just have a helper for all these navigates.
The implementation is a bit clunky because I had to access the validateSearch object to reconstruct the setters from the keys, and they're not typed, but I'm sure it could be improved.
I'd be very happy to have your opinion on this, suggestions to improve it, etc !
Thanks a lot
extended-salmonOP•3mo ago
I don't think this can work for every validateSearch, since it can be many things other than zod. Maybe with a proxy it could be made more generic. This is interesting though.
vicious-gold•3mo ago
indeed, that's definitely one of the shortcoming of such implementation, which works for me in practice but isn't general enough. But I guess knowing the innards of tanstack-router it should be possible to write it in a cleaner way.
extended-salmonOP•3mo ago
Here's a first draft for a
useSearchState
hook: https://github.com/TanStack/router/pull/4552
Sorry for the delay, I was playing Clair Obscur Expedition 33GitHub
feat(react-router): useSearchState by Sheraff · Pull Request #4552...
This is a 1st draft proposal for useSearchState: a hook designed to be used like a react useState, but manipulates route search params instead.
Example usage:
export const Route = createFileRoute(&...
extended-salmonOP•3mo ago
The implementation is actually better than what I initially proposed in this thread, because this PR made me write unit tests!
dependent-tan•3mo ago
cc @TkDodo 🔮
extended-salmonOP•3mo ago
also, please don't do a commit-by-commit review, because I mistakenly forked an old fork of the repo... The final diff on github is correct though, no worries
just FYI, i updated our implementation of useSearchState at work to use the same one as in this PR. If there's something fundamentally wrong with this, we'll know!