Wanted to get an opinion on my Query Keys
To get good at react query I am building a simple task management tool which has journeys. A journey is made up of stages and stages are made up of tasks. I was thinking of using the following query keys -
1. Query to get the journeys
2. Query to get a specific journey
3. Query to get stages of a journey
4. Query to get a specific stage of a journey
5. Query to get all tasks of a stage of a journey
6. Query to get a specific task fo a stage of a journey
Does this make sense? This follows the generic to specific model that was recommended in TkDodo's blog but I just wanted to get a sanity check since I made some mistakes before by having stuff like just ["stage", journeyId, stageId] instead of ["journeys", "journey", "stages", "stage", journeyId, stageId] which obviously is less powerful when invalidating queries since I can't invalidate multiple queries.
Also, I gave up adding workspaceId from queries 2 onwards since journeyIds are unique across workspaces. Does that make sense?
4 Replies
optimistic-gold•4y ago
I think I'd suggest reordering some of it a little bit.
1.
["journeys", "all"]
2. ["journeys", journeyId]
3. ["journeys", journeyId, "stages", "all"]
4. ["journeys", journeyId, "stages", stageId]
5. ["journeys", journeyId, "stages", stageId, "tasks", "all"]
6. ["journeys", journeyId, "stages", stageId, "tasks", taskId]
I think that hierarchy would suit you better because let's say you delete a stage, you'll want to invalidate #3 and delete #4, #5, and #6 under that stage. That could be accomplished like this:
If my understanding of hierarchical query keys is correct, that second one should get you what you need 😅correct-apricotOP•4y ago
That makes a lot of sense! Thank you!!
One followup question - When I click on a journey from the journey list, I useQuery on ["journeys", journeyId, "stages", "all"] to display all the stages and then in each stage component I useQuery on ["journeys", journeyId, "stages", stageId, "tasks", "all"]
to show its tasks. When I add a stage or task, I only need to invalidate the above queries respectively. All of this makes sense to me.
My confusion arises when I need to update a stage or task. For example, if I wanted to update a task, I could just filter through the array of tasks (which I get from ["journeys", journeyId, "stages", stageId, "tasks", "all"]) based on its id, replace it in the cache and then invalidate ["journeys", journeyId, "stages", stageId, "tasks", "all"]. With this logic I don't need queries 4 and 6.
Does it make sense to just avoid queries 4 and 6 or should each stage/task also have its own query? I initially though it might make sense to have them since I could make everything more modular but I don't see the benefit of making things more modular since I anyway have to invalidate the "all" queries . Would appreciate any thoughts you have on this!
optimistic-gold•4y ago
You need the "all" queries because at request time you probably don't know the IDs of the individual tasks/stages/journeys, so you wouldn't be able to make the query keys for them.
But it sounds like you're thinking "Hey I already have this task loaded in the cache, it kinda sucks that I have to refetch the task on the individual task component" which is totally valid. You can seed
initialData in your individual queries by looking in the "all" queries for the corresponding items.
(I recommend using initialData() as a function in this case so that you're only getting stuff out of the query cache when a new query mounts instead of every render)
If you have a default staleTime configured then it'll use any non-stale data that already exists in the cache. This next part I'm not as sure about: I think if both the task list component is still mounted (making the task list query active) and then the individual task component mounts after the data is loaded, then you won't need staleTime. I'm not sure what best practices are with staleTime but FWIW my app at work uses 2 minutes as the default. 🤷♂️
As for your query invalidation/setting question--afaik you don't need to (and shouldn't) both update query data and invalidate it. Invalidation is for when you want to trigger another fetch because you don't know what the new value would be.
Just doing a mutation like this will be what you're looking for I think:
Just set to the individual query and update the list with the updated value. No need to invalidate. If you're doing a delete or a create, I usually invalidate the "list" endpoints because I usually have pagination and changing the number of items could result in stuff moving to a different page, so there's no way to update it in that case.
Er, looks like I misunderstood the first bit. You wanted to nix the individual queries not the all queries. You could do that with a selector function, but that would require that you fetch all the items every time instead of just the items you need, which may be annoying/unnecessary.
Something like that if you wanted to go that route
FWIW I prefer the all + individual pattern with initialData.
One of my favorite things about RQ compared to other more global state management solutions is that you don't care what order the data is loaded; you don't have to make assumptions about what state your app is in. "Well if I'm on the profile that has the app header so the user will already be loaded but if I'm on the home page the user might not be loaded, so I need to check it then fetch if it's not there"--none of that! Every component just uses whatever queries they need and if there's already data loaded then they get to share. No need to fetch a bunch of crap when your app mounts or check what state your app is in, stuff gets loaded whenever the first component wants it.
So along those lines I wouldn't load extra data--like a whole list of tasks if you only care about just one of them--but seeding from a query that likely already has data is the best of both worlds. And if there isn't data already loaded then it'll just do the fetch for the single item.correct-apricotOP•4y ago
Makes a lot of sense. Thank you so much @SuperPizza for the detailed responses. I will implement some of the ideas mentioned and hopefully they work. Appreciate it though!