BeardMagician
BeardMagician

Reputation: 657

How do I use react-query to synchronously join data from different react-query hooks together?

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

Answers (1)

devuxer
devuxer

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

Related Questions