T
TanStack2y ago
xenial-black

Why don't my routes' error and loading components respond predictably to react-query devtools

I'm really not sure if this post belongs here or in the query section so apologies if the coin flipped to the wrong side. I would like to be able to trigger the rendering of the errorComponents and pendingComponents by clicking on the corresponding triggers in the react-query dev tools but I'm getting mixed results. Here's my simplified Codesanbox: https://codesandbox.io/p/github/trevorfehrman/tanstack-router-example/main?import=true I'm looking specifically at the load-todo route.tsx and index.tsx files. To reproduce, direct the browser to that route and open the react-query dev tools, and select the todo query. Trigger Error: This trigger successfully sets the status of the query to "error" and conditionally renders an element hidden behind the isError boolean, but it doesn't trigger the errorComponent. Throwing an error from the underlying query function does trigger a render of the errorComponent but only if it's defined in the route.tsx file and not the index.tsx file. Question 1: Is it possible to use the react query dev tools to trigger the rendering of the errorComponent? Question 2: Why do I have to define the errorComponent in the route file instead of the index file for a thrown error to trigger its rendering? Trigger Loading: Contrary to the Trigger Error button, the Trigger Loading button does trigger the rendering of the pendingComponent, even if it's defined in the index file. However, it doesn't appear possible to restore the loading state. It locks up the dev tools and I have to refresh the app (see gif). Question: is there a way to restore the loading state to false from the dev tools without refreshing the page? Thanks in advance for any guidance anyone might have : D
No description
23 Replies
eager-peach
eager-peach2y ago
The question regarding restoring the buttons on the query devtools, is probably something to be filed with folks at #query. I believe the router's pending state gets trigger because it throws a promise which triggers Suspense.
xenial-black
xenial-blackOP2y ago
I will ask on the other channel, thank you sir : D
eager-peach
eager-peach2y ago
By the way, you want to do your data loading inside the loader and not the beforeLoad callback. Plus you can use the beforeLoad callback to reduce amount of imports you make around the application. https://stackblitz.com/edit/github-osigxs?file=src%2Froutes%2Fload-todo%2Findex.tsx
Sean Cassiere
StackBlitz
working query - StackBlitz
Run official live example code for Router Basic React Query File Based, created by Tanstack on StackBlitz
eager-peach
eager-peach2y ago
Sorry I moved your example to Stackblitz, but CSB was being a pain and was outright losing the progress I'd made every 2 minutes.
xenial-black
xenial-blackOP2y ago
Yeah CSB is being weird rn, thanks for moving to SB. So I have that data fetching in the beforeLoad callback because I wanted to merge the data into context per my earlier question you helped me with: https://discord.com/channels/719702312431386674/1221554798080295123 Is there a better way to do this? Excellent suggestion about putting the query options into context rather than importing twice. That will be a mental adjustment
eager-peach
eager-peach2y ago
It kind of depends what you are putting into the context. In the example above, since your are anyways using Query (with its excellent caching), it makes sense to just pass down the queryOptions and consume it using suspense. For example:
// src/routes/posts.$postId.tsx
const Route = createFileRoute({
beforeLoad: ({ params }) => {
return {
postIdOptions: makePostIdOptions(params.postId)
}
}
})
// src/routes/posts.$postId.tsx
const Route = createFileRoute({
beforeLoad: ({ params }) => {
return {
postIdOptions: makePostIdOptions(params.postId)
}
}
})
IMO, when using it with Query the router's context is better for passing down metadata which can construct query calls.
xenial-black
xenial-blackOP2y ago
Yeah point taken, I think your suggestion is the right approach in the general case but I might have a weird case. The app I'm building for which this code is a PoC traverses through many screens and forks collecting a little data at a time. At the end of the line I need to collect all that data in one payload and submit it to an API. By accumulating all that data in router context, passing it down from parent route to child route, my hope is I'll be able to get a fully type-safe object on the final screen. The current iteration of the app simply has one top level context provider which accepts a huge type of which all the properties are optional since before the user starts their flow none of the data has been collected yet and is thus obviously undefined. This requires us to do all sorts of optional chaining and non-null assertions down the tree. Going with your suggestion, if I'm following what you're saying, I could just pull all the data out of the query cache on the final screen. I think that would work but wouldn't I lose some type safety that way? I think I would still have to null check all of them, for example.
eager-peach
eager-peach2y ago
You shouldn't lose type-safety, since the ensureQueryData call will fail if it cannot fetch and validate that data. That error can be used to show something being wrong (error screen) or redirect to the correct screen. In the Stackblitz example, you'll notice that type-safety isn't lost at any point. This'd of course be a decent bit earlier if you were using search params, but I'm sure you have a reason for not going down that path.
xenial-black
xenial-blackOP2y ago
Actually using search params was my first idea, but that introduces a serialization gap. At each screen I would have to validate the search params again. With Zod that wouldn't be that bad since I can just extend the schema from the previous screen...and actually now that I think about this in the context of your advice that might be worth strongly reconsidering because I could pass the validation schemas down the context tree which would make maintaining them much easier. Very interesting food for thought 🤔 You're totally right about the type safety, and by that I really just mean assurance that the data is defined, not the types per se, getting maintained due to the ensureQueryData, that hadn't ocurred to me. On the final screen it wouldn't be difficult to compose those queries together, maybe I could even use some variation of the query factory Tkdodo has written about a couple of times on his blog. If that works you might be right that this is the best approach. It avoids doubling the data, one set in the query cache and the other in the router context (or alternatively the search params). Out of curiosity, is there anything unambiguously wrong about fetching data in the beforeLoad callback? It seems to trigger suspense in the same way the loader callback does?
eager-peach
eager-peach2y ago
Nothing that I can think of off the bat that'd be explicitly against using the beforeLoad for data fetching, other than how it could play with the preload behaviours and that isn't what it was meant for. Unlike your loader which can have cache staleTimes and whatnot, your beforeLoad callback really is meant to be something that does some processing quickly before the actual loader is called for the route. Its one of those, I can't NOT tell you that you shouldn't be using it for that.
xenial-black
xenial-blackOP2y ago
but couldn't you not tell me i shouldn't not...don't...? But seriously thanks for the advice, I'll try the search params version and the query cache version and compare all 3
eager-peach
eager-peach2y ago
Its one of those mixing of potions to get the right spell.
xenial-black
xenial-blackOP2y ago
Yeah I really want to get this as tight as possible because if it's good enough there are a bunch of other apps at work it might apply to.
eager-peach
eager-peach2y ago
The way I approach the passing data between routes question is actually pretty simple in that regards. Is it remote data? Yes - Just fetch it again or pull from cache No - Therefore its user input Is the user input actually sensitive? Yes - Use zustand (or some store/context) No - Just use search params And if its a mix of both, I treat the remote data as user input and then select between the options. I treat the router context as a sort of way to pass the correct configuration to the route... If that makes sense. Like, search param Y is missing, so foo=false, or the postIdOptions example from earlier.
xenial-black
xenial-blackOP2y ago
So this app runs on a cash register. The little bits of data it collects on the way come from the various peripheral devices that connect to the register, handheld scanner, credit card scanner, check scanner, etc. The app communicates to these devices via http requests so in a sense it's remote data but in another sense it's user input. We opt out of the cache in any normal sense as this data will never be "stale" and only rarely will we want to even navigate back, and in the cases we do we almost certainly want to trigger the requests to the device(s) coupled to that screen anew. So in your reckoning I think it's a mix of both and I intuitively like the idea of context as being a pipe for tools rather than data, particularly if I have that data safely stored in a global bucket (in this case react-query cache). The main thing I want to avoid is having to do any kind of validating of the data when the user reaches the final screen and all the data has been accumulated. That's the way we have it now and it's maintainability is a major headache and source of bugs.
eager-peach
eager-peach2y ago
I do think regardless of the approach you take, there's going to be some level of data validation regardless of which path you take. Its more a matter of how tedious is that validation? and how easy is it to understand when something goes wrong. Since you control the flow of the app, I'd probably recommend just use zod for your data validations, see how far that takes you. It the case where a user directly goes to page where you'll need to handle the failed validation case, but that is sort of an intentional wretch being thrown in works, so you have some latitude in that regards.
xenial-black
xenial-blackOP2y ago
i want to validate exactly once with zod in the query function and then for my app to know those values must be not only of the right shape but also defined on some given leaf of the user flow tree in this case the user can't possibly get to a page in any other way than the app dictates they can, the way these apps are rendered on the register you can't even see the URL bar let alone type something into it these apps are similar to state machines in that way
eager-peach
eager-peach2y ago
Then in that case, you could have something like
/entry?seedData=adksal.....asdas <- entry.route.tsx
/entry/?seedData=^^^
/entry/foo?seedData=asdadada....dadsad&progressData=adasdsa....dasdsadsad
/entry?seedData=adksal.....asdas <- entry.route.tsx
/entry/?seedData=^^^
/entry/foo?seedData=asdadada....dadsad&progressData=adasdsa....dasdsadsad
Where you navigate to the entry index page you'll be required to set that seedData, across each screen you'll just be updating the progressData with the exact fields you want. Also, give you the benefit of you being able to recover from a fatal error by just reseting to the seedData.
xenial-black
xenial-blackOP2y ago
yeah, that was pretty much my first plan lol. it'll be funny if after all my other explorations i come full circle back to that one
xenial-black
xenial-blackOP2y ago
Following up on this here. I had a brief exchange with TkDodo on the react-query board. It turned out the issues with the loading action was a bug which they've promptly fixed. https://discord.com/channels/719702312431386674/1222015700370194452 In that thread he provides a very simplified example of the RQ devtools triggering the error boundary as expected. I experimented a bit more with their integration with the error component in tanstack router and am still not able to get it to behave. If I throw an error from the query function manually the error boundary fallback triggers as expected but the devtools don't seem to throw an error in a way that the error component catches. I still am not sure if this is the right place to ask about this, but I did notice that even on the tanstack-router docs this isn't working in the provided examples. If you take the kitchen sink example: https://stackblitz.com/github/tanstack/router/tree/main/examples/react/kitchen-sink-react-query-file-based?embed=1&theme=dark And trigger the error via the dev tools for any of the queries we don't fallback to the error component.
StackBlitz
Router Kitchen Sink React Query File Based Example - StackBlitz
Run official live example code for Router Kitchen Sink React Query File Based, created by Tanstack on StackBlitz
xenial-black
xenial-blackOP2y ago
given that i can trigger the error component by merely throwing an error from within my query function this is a pretty nitpicky devex issue, but it's weird, isn't it? I would think the error boundary would work the same as the suspense boundary
eager-peach
eager-peach2y ago
External Data Loading | TanStack Router Docs
⚠️ This guide is geared towards external state management libraries and their integration with TanStack Router for data fetching, ssr, hydration/dehydration and streaming. If you haven't read the standard Data Loading guide To Store or to Coordinate?
GitHub
feat: add reset prop to errorComponent (#1358) · TanStack/router@9f...
* feat: add reset prop to errorComponent * fix: types of defaultErrorComponent * fix: put match into pending state when being invalidated if it was in error state * docs: reset * docs: ...
xenial-black
xenial-blackOP2y ago
hm, this is interesting but I think the trouble I'm having is getting the error component to mount at all. This is still extremely valuable, I probably was going to need to know this next, but unless I'm misreading this doesn't pertain to the react-query devtools error action not triggering the error component rendering in the first place. p.s. not sure how much of this thread you have fresh in your memory but i tried all three of the approaches we talked about and simply passing the query options via the router context and relying on the query cache like you suggested in your stackblitz was definitely the best option, so thank you : D

Did you find this page helpful?