How to implement multiple api call in a single query with RTK query
Asked Answered
H

1

13

I'm new in RTK Query and I'm struggling with a use case I have to implement.

Scenario: I have to merge the results from two API calls: the first API call is a private API call while the second one is a public API call. I need to merge the responses from these two APIs and write the computed result into the RTK cache so the UI can update accordingly.

Problem: I'seeing that as soon as the await queryFullfilled is invoked, RTK Query immediately write into its cache the response from that API call and then when I make my calculation and try to update the RTK cache with apiSlice.util.updateQueryData the cache will change again. That's means that the UI will render twice, the first time using a wrong value (an array of persons) and the second time with the correct value (the JSON composed by ids and entities).

Question: Is there a way to have just 1 write into the RTK cache so I can have just the computed value I need ? Because what is happening is that for some instances I'm having into the cache an array while I need the {ids: [...], entities: {}} JSON.

import { createEntityAdapter } from '@reduxjs/toolkit';
import axios from 'axios';

export const personsAdapter = createEntityAdapter();
const permitsInitialState = personsAdapter.getInitialState();

export const apiSlice = myServiceApi.injectEndpoints({
  endpoints: (builder) => ({
    getPersons: builder.query({
      query: () => ({ url: '/persons', method: 'get' }),
      onQueryStarted: async (_, { dispatch, queryFulfilled }) => {
        try {
          // Resolving the private API call
          const { data: persons } = await queryFulfilled;

          // Just a random public API call
          const { data: todos } = await axios('https://jsonplaceholder.typicode.com/todos');

          const enhancedPersons = /** Here the logic that merge the todos and the persons */

          const state = personsAdapter.setAll(permitsInitialState, enhancedPermits);

          dispatch(
            apiSlice.util.updateQueryData('getPersons', _, (draft) => {
              Object.assign(draft, state);
            })
          );
        } catch (e) {
          console.error(e);
        }
      },
    }),
  }),
});
Hydrosome answered 29/4, 2022 at 9:49 Comment(1)
Facing same challenge especially when as the first answer, in my case type are not same and the cast can't be made and the call are both get with an id. Going to post here my solution once I will make itDitchwater
D
15

That is one of the use cases of queryFn: Performing multiple requests with a single query

import {
  createApi,
  fetchBaseQuery,
  FetchBaseQueryError,
} from '@reduxjs/toolkit/query'
import { Post, User } from './types'

const api = createApi({
  baseQuery: fetchBaseQuery({ baseUrl: '/ ' }),
  endpoints: (build) => ({
    getRandomUserPosts: build.query<Post, void>({
      async queryFn(_arg, _queryApi, _extraOptions, fetchWithBQ) {
        // get a random user
        const randomResult = await fetchWithBQ('users/random')
        if (randomResult.error) throw randomResult.error
        const user = randomResult.data as User
        const result = await fetchWithBQ(`user/${user.id}/posts`)
        return result.data
          ? { data: result.data as Post }
          : { error: result.error as FetchBaseQueryError }
      },
    }),
  }),
})
Delibes answered 29/4, 2022 at 10:30 Comment(3)
Thanks a lot, I miss completely that section of the documentation !! I'll try asapHydrosome
instead of the fetchWithBQ is there any way to reuse another existing endpoint? And just await for its data?Convulsive
No. Such cascades are deliberately not part of the library, as they would need a lot of additional tracking & re-firing if another cache values is updated etc.Delibes

© 2022 - 2024 — McMap. All rights reserved.