Only update affected post in infinite feed?
Imagine the following scenario:
1. user visits a page w/ an infinite posts feed
2. user scrolls down for a while, see a post they like, and upvote it
3. only the relevant data updates & only the relevant component re-renders
How would you implement this? Image shows an implementation example, but it look terribly inefficient to update the reference to the entire paginated object.

12 Replies
afraid-scarlet•2mo ago
I am in no way shape or form sure if this is best practice, but I have done something similar to this, for a more niche scenario where it made more sense, but here goes:
In the infinite query, add logic to your
fetchPosts() method to setQueryData for individual posts. ie: your current fetchPosts uses the ['posts'] cache key, make it so that after the fetch call in fetchPosts, it sets query data to ['posts', '<post id here>'] by looping through the results.
Then in your <Post> component, you would just pass the post ID, and inside of that component you would just do a react query targeting that cache key (you can mark it as enabled: false so it never actually triggers the API call, but it would read from the react query cache
Then on upvote, your essentially now only modifying the query data for that individual cache key, instead of the global posts oneharsh-harlequin•2mo ago
that might be very good practice actually. sounds like it's great implementation of client state - it prevents re-renders, enables not passing props too deeply, and doesn't depend on other state management libs, which you'd need to use if not using tanstack query in that case if you want to prevent re-renders
afraid-scarlet•4w ago
I would suggest the most idiomatic way of doing this would be to have the components that render each post only use a select function so that any un-updated posts don't change. See Dominik's blog post on the topic: https://tkdodo.eu/blog/react-query-selectors-supercharged
And for an example:
Now when you use just one post at a time in your components, only the affected post will return a new object and only the components subscribed to that post will rerender.
React Query Selectors, Supercharged
How to get the most out of select, sprinkled with some TypeScript tips.
vicious-goldOP•4w ago
thanks, i've been implementing this solution, but found a problem with it: garbage collection
in my use case, the list of posts is an infinitely-loaded, virtualized list, meaning post components aren't rendered until they're scrolled to, which means that the useQuery calls on those posts are never executed, causing those manually-set posts to get garbage-collected if they're not scrolled to within 5 minutes after setQueryData
afraid-scarlet•4w ago
I assume that can be disabled?
vicious-goldOP•4w ago
@happy not entirely: in
useQuery you can set gcTime: Infinity to disable garbage collection, but that will only cover cases where you the component DOES render within 5 minutes after setQueryData, but not the cases where it doesn't
a solution that seems to work for me so far is to setQueryData just before rendering the actual post component, as opposed to in the queryFn as you suggested
harsh-harlequin•4w ago
queryClient.setQueryDefaults("post", { gcTime: Infinity } - maybe that would solve the workaround of setting it "just before rendering"?
since queryData allow you to only change data but withDefaults you have access to rest of the optins
so you would do that before queryClient.setQueryData in parent componentharsh-harlequin•4w ago
it actually took me a while to think how to update that default behaviour, and zookerDude also not noticing you don't need to
useQuery to set the gcTime makes me think docs can be improved
@TkDodo 🔮 what do you think about updating from:
If the query is not utilized by a query hook in the default gcTime of 5 minutes, the query will be garbage collectedto
If the query is not utilized by a query hook in the default gcTime of 5 minutes, the query will be garbage collected (which can be overwritten with queryClient.setQueryDefaults)

ambitious-aqua•4w ago
I'm sorry, guys, if I'm late to the party, but I think normalization can come to the rescue. The trick is to maintain an array of
ids that you will map and render, and individual post queries by their id that you will pull using useQuery. Here's the pseudo-code to describe the concept:
Both hooks should have gcTime: Infinity.harsh-harlequin•4w ago
i dont think 'post' will have gcTime infinite without setting defaults like i said
since useSinglePost won't be called cause Post is not rendered
vicious-goldOP•2w ago
thanks @konhi, setQueryDefaults seems to be the way: i've implemented it on individual posts, and so far not getting the error from before
i hope there's not a serious performance implication with this? i'm relatively inexperienced in dev, so i'm not sure
harsh-harlequin•2w ago
you'll never know until you benchmark it!
but i feel usually in web dev, if there are performance issues, then it's clearly visible