Reputation: 657
I have three different data types: Books
, Authors
, and Publishers
. I use react-query to fetch a list of each type, i.e. useBooks(bookIds)
, useAuthors(authorIds)
, usePublishers(publisherIds)
and this works great when I try to use them independently of each other.
However, a book has a list of authorIds
and publisherIds
and I want to join the Publishers
and Authors
objects to the Books
. I can't figure out the right way to do this using react-query. Ideally I'd like to be able to call all three hooks, wait for their response, and then return it as one big hook. However, I cannot wrap my head around to do this.
One implementation I tried was to make each hook that joins data to Book
dependent on the results of a previous hook. This works okay; however the big problem with this is that the joins are done asynchronously from one another; the data the component consumes cannot expect a Book
to have a Author
or Publisher
once the data is returned. Here's an example:
const usePopulatedBooks = (bookIds) => {
const booksQuery = useBooks(bookIds);
const {data: books} = booksQuery;
const authorIds = [];
const publisherIds = [];
if (books) {
books.forEach(book => {
authorIds.push(book.authorId);
authorIds.push(book.publisherIds);
})
}
// Who knows when this finishes?
const {data: authors} = useAuthors(authorIds);
// Who knows when this finishes?
const {data: publishers} = usePublishers(publisherIds);
// Adds "author" and "publisher" field (if the authors/publishers array exist) to "book"
const joinedBooks = joinData(books, authors, publishers);
return {...booksQuery, data: joinedBooks};
}
Is there some way I can write the above logic in a way such that nothing returns until all data has been joined? In my head the way I'd do this is call all three hooks in a query function of another react-query hooks, but hooks rules don't let me do this.
Upvotes: 7
Views: 11773
Reputation: 42364
Without knowing how your useBooks
, useAuthors
, and usePublishers
hooks work internally, I can't answer precisely, but I'd approach the problem by writing a function that does all your API calls, then wrap that single function with useQuery
. You don't want to conditionally call different hooks as that violates one of the rules of hooks: "Don’t call Hooks inside loops, conditions, or nested functions."
Something like this:
async function getBookDetails() {
const books = await getAllBooks();
const authorIds = books.map(x => x.authorId);
const authors = await getAuthorsForIds(authorIds);
const publisherIds = books.map(x => x.publisherId);
const publishers = await getPublishersForIds(publisherIds);
return { books, authors, publishers };
}
Then you can wrap it in a useQuery like this:
const result = useQuery("book-details", getBookDetails);
This assumes that if there are no results for any of the queries, you get an empty array []
, not null or undefined.
If you're not familiar with Array.map
, you can read up on it here.
Upvotes: 9