How to fetch n dependent data with react-query
Asked Answered
M

6

15

I need help with react-query library. I fetch a list of objects and then for everyone I need to fetch additional data from another service. How should I do this with react-query useQuery hook?

Example 1:

const { data} = useQuery("USERS", fetchUsers);
data.map(user => useQuery(["ACCOUNT", {userId: user.id}], 
                 ({userId}) => fetchAccount(userId)));

Error:

ESLint: React Hook useQuery cannot be called inside a callback. React Hooks must be called in a React function component or a custom React Hook function. (react-hooks/rules-of-hooks)

Melodie answered 17/2, 2020 at 10:41 Comment(0)
E
24

This looks to be the current solution for at least react-query v3:

 // Get the user
 const { data: user } = useQuery(['user', email], getUserByEmail)
 const userId = user?.id
 
 // Then get the user's projects
 const { isIdle, data: projects } = useQuery(
   ['projects', userId],
   getProjectsByUser,
   {
     // The query will not execute until the userId exists
     enabled: !!userId,
   }
 )

 // isIdle will be `true` until `enabled` is true and the query begins to fetch.
 // It will then go to the `isLoading` stage and hopefully the `isSuccess` stage :)

From dependent queries doc.

Erinaceous answered 17/2, 2021 at 16:51 Comment(0)
M
9

I found the anwser provided by the author of react-query:

From what you've described, this appears to be the classic "I want to use React hooks in array-mode", which we've definitely all been in before.

99.9% of the time, I feel the answer to this predicament is to render hook usages into multiple components and compose their results post-render, but there are definitely edge cases here and plenty of tradeoffs. However, it's the best solution we have currently since there is no great way to useMapHooks(item => useQuery(item), items). That would be really great, but trust me, I've been down that road and while I did get something to work more or less, it ended up complicating my code much more than I expected. For the time being, React Query will likely not get the feature you're talking about, since the library itself uses hooks, it's bound by the same mechanics you are and would require massive amounts of rearchitecting to even get somewhat close to what you're describing. The return on investment would not be worth it and would likely degrade the rest of the library in both performance, size and API complexity.

To solve your specific problem for today, I would suggest three possible solutions:

  1. Be okay with multiple columns info being requested even though you only need one. Adjust your memoization/consumption of those column infos to only update if they are new. This will keep things drastically simple and probably let you move forward faster.

  2. Compose your column info fetcher queries into multiple components that don't render anything and instead pass them a prop that allows them to report their results back up to the parent component, then compose their results in the parent component into a single array of columns.

  3. Create your own little "cache" to hold your list of already-fetched column information, then combine your fetching logic into a single query. Use your custom cache object to fill in the slots in that array when the data is available and also keep it up to date by clearing the cache as often as you see fit. I would consider this a last resort, since you would essentially start managing your own custom cache, which is exactly what React Query is meant to do for you. If you do get here, I would suggest weighing the benefits your getting with React Query here overall. It may be one edge case where React Query's caching/fetching strategy isn't a good fit, which is fine.

Hope this helps!

https://github.com/tannerlinsley/react-query/issues/138#issuecomment-589709270

Melodie answered 21/2, 2020 at 16:41 Comment(0)
C
7

Use dependent queries, -- until data (users) is resolved, then your second query key string should be falsy until you have users:

// rename `data` to `users`
const { data: users } = useQuery("USERS", fetchUsers);

// `users` causes query key to be falsy until they are loaded
// rename `data` to `accounts`, map `users` id only and pass to pass in as param
const { data: accounts } = useQuery([users && "ACCOUNT", users.map((u) => u.id)], async (key, userIds) => {
  const response = await fetchAccounts(userIds);

  // TODO any mapping?

  return response;
});

https://www.npmjs.com/package/react-query#dependent-queries

Cullender answered 22/5, 2020 at 22:21 Comment(5)
That's not my case :) In my example I don't fetch all accounts at once. I would like to fetch account one by one and cache them one by one in separate cache key for every user.Indoeuropean
Updated link: react-query.tanstack.com/docs/guides/queries#dependent-queriesUnreasonable
@MichałMielec I have the same issue, did you manage to find out how to do so?Elizabethelizabethan
@Elizabethelizabethan I posted explanation given by the author of the library. In short it is not possible. https://mcmap.net/q/760463/-how-to-fetch-n-dependent-data-with-react-queryIndoeuropean
Updated link: react-query.tanstack.com/guides/dependent-queriesLuck
E
0

I've started using another technique for this where I do both calls from the "get" function. The advantage is hiding the API complexity behind the function. The disadvantage is having less granular control on the component.

Below is an example (not tested) similar to what I usually do.

 const getProjectsByUser = () => {
   // I'm using axios, but you can use fetch or something else.
   const {data} = axios.get("/api/user")
   
   // Let's assume the user endpoint returns a user with an array of projects
   const projectNames = data.user.projects

   const fetchProjects = projecNames.map(name => axios.get(`/api/projects/${name}`)
   
   // I may use Promise.all or Promise.allSettled
   const projResponses = await Promise.all(fetchProjects)

   return projResponses.data;
 }

The only code on some component:

 import {getProjectsByUser } from "some path"

 const { data: user } = useQuery(['user', email], getProjectsByUser)
 const userId = user?.id
 
Erinaceous answered 14/7, 2021 at 8:49 Comment(0)
D
0

I am doing this just code for me it works perfectly react dependent query doc

const { yokooUser } = useFirebase()
    // load all bookings using react query nad current user email

const {  isError, error, status,  data } = useQuery(
    ['myOrders', yokooUser.email], async () => await axios.get(`http://localhost:5000/booking/${yokooUser.email}`,
        {
            // The query will not execute until the userId exists
            enabled: !!yokooUser.email,
        }
    ))

if (status === 'loading') {
    return <div>Loading...</div>;
}
// error message query
if (isError) {
    return <div>Error: {error.message}</div>;
}
Dethrone answered 25/8, 2022 at 19:50 Comment(0)
R
0
    import { useQuery } from 'react-query';

const fetchData = async (url) => {
  const response = await fetch(url);
  return response.json();
};

const ParentComponent = () => {
  const { data: firstData, refetch: refetchFirst } = useQuery('firstQuery', () => fetchData('/api/first'));

  const { data: secondData, refetch: refetchSecond } = useQuery(
    ['secondQuery', firstData],  // Invalidate on changes to firstData
    () => fetchData(`/api/second/${firstData.id}`),
    {
      enabled: !!firstData,  // Only enable when firstData is available
    }
  );

  // Additional nested queries...

  return (
    <div>
      {/* Render based on firstData and secondData */}
    </div>
  );
};

    
Roseline answered 9/11, 2023 at 12:49 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.