T
TanStack17mo ago
noble-gold

How to 'select' data from the cache? (read-only in a reactive way)

Hello, I am using react query as my state manager. I have one query that fetches the entire user's state from the server (includes things like preferences and user generated content like cards, lists, spaces etc...). In my app, I have many different components that need to be wired up to different pieces of data fetched from that one query. So my setup is: - have one useQuery call sit at the top of the component hierarchy. This query is responsible for fetching the data (enabled is true). - have multiple useQuery calls with { enabled: false } in child nodes so we can read only from the cache and not trigger any refetches. I thought this would be fine but it seems that my app is having some performance issues and I am suspecting that all those disabled queries are leaving behind lots of detached DOM nodes (as shown in a heap snapshot). What are your thoughts on the topic? Is there a better way to handle this? Essentially I need a pattern similar to redux's select: where I can efficiently read from the data store in a reactive manner (component is re-rendered when data is updated). Unfortunately I have to stick to this mental model as this project is part of a large refactor to remove redux from our codebase and replacing it with react query due to other technical reasons. Thank you for your assistance!
4 Replies
stormy-gold
stormy-gold17mo ago
have multiple useQuery calls with { enabled: false } in child nodes so we can read only from the cache and not trigger any refetches.
It should be safe for the children to call useQuery, and so long as they use the same query key then they will read the data from the cache and they won't refetch. Typically a refetch only occurs when you invalidate the queryKey by calling invalidateQueries or if the user switches tabs. If you're moving back and forth from the devtools it will look like things are refetching because moving focus from the page into the devtools causes React Query to refetch (it's similar to switching tabs). TkDodo has a blog post about this where they describe creating a Redux selector's like API: https://tkdodo.eu/blog/react-query-data-transformations#3-using-the-select-option
export const useUser = (userId: number, select) => {
return useQuery({
queryKey: ['users', userId],
queryFn: () => fetchUser(userId),
select
})
}

// Select the user's friends
export const useUserFriends = (userId: number) => {
return useUser(userId, (data) => data.friends)
}

// Select the user's name
export const useUserName = (userId: number) => {
return useUser(userId, (data) => data.name)
}
export const useUser = (userId: number, select) => {
return useQuery({
queryKey: ['users', userId],
queryFn: () => fetchUser(userId),
select
})
}

// Select the user's friends
export const useUserFriends = (userId: number) => {
return useUser(userId, (data) => data.friends)
}

// Select the user's name
export const useUserName = (userId: number) => {
return useUser(userId, (data) => data.name)
}
React Query Data Transformations
Learn the possibilities to perform the quite common and important task of transforming your data with react-query
stormy-gold
stormy-gold17mo ago
I guess the main downside is if you have a lot of arguments that need to go into the useUser queryKey, it could get cumbersome passing those around to all of the selectors. I don't know if this is a bad idea or not, but if the parts of the queryKey that you need are in the URL then you could maybe put them inside of the useUsers hook like:
export const useUser = (select) => {
// If you're using react-router you could do something
// like this with useParams()
// https://reactrouter.com/en/main/hooks/use-params
const { userId } = useParams()

return useQuery({
queryKey: ['users', userId],
queryFn: () => fetchUser(userId),
select,
enabled: !!userId
})
}

// Now this hook doesn't have to pass in the userId
export const useUserFriends = () => {
return useUser((data) => data.friends)
}
export const useUser = (select) => {
// If you're using react-router you could do something
// like this with useParams()
// https://reactrouter.com/en/main/hooks/use-params
const { userId } = useParams()

return useQuery({
queryKey: ['users', userId],
queryFn: () => fetchUser(userId),
select,
enabled: !!userId
})
}

// Now this hook doesn't have to pass in the userId
export const useUserFriends = () => {
return useUser((data) => data.friends)
}
noble-gold
noble-goldOP17mo ago
I like this, thank you!
noble-gold
noble-goldOP17mo ago
We're using https://github.com/HuolalaTech/react-query-kit to manage the query keys in a typesafe manner.
GitHub
GitHub - HuolalaTech/react-query-kit: 🕊️ A toolkit for ReactQuery t...
🕊️ A toolkit for ReactQuery that make ReactQuery hooks reusable and typesafe - HuolalaTech/react-query-kit

Did you find this page helpful?