multiple (dynamic) calls of RTK Query

394 views Asked by At

I want to fetch multiple datasets data in a single React component.

When there is only one dataset, it's simple by using the following RTK query.

const { 
    data: deviceRecord, 
} = useDeviceRecordListQuery({ deviceId: ID, ... }, {
    selectFromResult: result => ({
        ...result,
        data: result.data?.map((res) => ({
            x: res.x,
            y: res.y,
        }))
    }),
    skip: !chartData 
}) 

But when there are multiple datasets data, I am not sure how to achieve that with RTK query as the number of datasets fetched changes dynamically. The dataset fetched is determined by ID like this and comes as a prop from parent component

const data = [
    {ID: 1},
    {ID: 2},
    ...
]

I was thinking using the RTK query call in a loop somehow, but because the rules for hooks, I can't.

It is not important, but the result of my effort should be a single Line chart (ChartJS library) with multiple data lines.

EDIT: Here is the api slice:

import { citywebApi } from "../../openapi/citywebApi"
import { processError } from "../../utils/errors"

export const devicesImportChartsApiSlice = citywebApi.enhanceEndpoints({
    addTagTypes: ['devicesImport'],
    endpoints: {

        deviceRecordList: {
            async onQueryStarted(_, { dispatch, queryFulfilled }) {
                try {
                    await queryFulfilled                    
                } catch(error) {
                    processError(error, dispatch, 'err msg')
                }
            },
            providesTags: ['devicesImport']
        },
        
    }
})

export const {
    useDeviceRecordListQuery
} = devicesImportChartsApiSlice

and here is a part of the api auto generated file:

import { emptySplitApi as api } from "./emptyApi";
const injectedRtkApi = api.injectEndpoints({
  endpoints: (build) => ({
...
    deviceRecordList: build.query<
      DeviceRecordListApiResponse,
      DeviceRecordListApiArg
    >({
      query: (queryArg) => ({
        url: `/api/device_record/list/device_id/${queryArg.deviceId}`,
      }),
    }),
...
  }),
  overrideExisting: false,
});
export { injectedRtkApi as citywebApi };
...
export type DeviceRecord = {
  device_id: number;
  unit_type: string;
  time_utc: string;
  value: number;
};
export type DeviceRecords = DeviceRecord[];
export type DeviceRecordListApiResponse = DeviceRecords;
export type DeviceRecordListApiArg = {
  deviceId: number;
  ...
};

the result data should be array (representing chart) containing arrays (representing each dataset/line in the chart) and the objects are Cartesian values for that dataset:

[[{x:1697656739, y:10},{x:1697657739, y:15},...],[{x:1697656739, y:5},{x:1697657739, y:1},...],...] 
1

There are 1 answers

1
Drew Reese On

I would suggest updating the deviceRecordList endpoint to consume an array of device ids and use a queryFn function to enqueue multiple backend requests, consolidating the responses into a single array.

See Performing Multiple Requests with a Single Query for more details.

An example implementation may look similar to the following:

import { emptySplitApi as api } from "./emptyApi";

export type DeviceRecord = {
  device_id: number;
  unit_type: string;
  time_utc: string;
  value: number;
};
export type DeviceRecords = DeviceRecord[];
...

const injectedRtkApi = api.injectEndpoints({
  endpoints: (build) => ({
    ...
    deviceRecordList: build.query<DeviceRecords, number[]>({
      queryFn: async (arg, api, extraOptions, baseQuery) => {
        const requests = arg.map(id => baseQuery({
          url: `/api/device_record/list/device_id/${id}`,
        }));

        const results = await Promise.all(requests);

        // results is an array of DeviceRecord[], flatten
        // to single DeviceRecord[]
        return results.flat();
      },
    }),
    ...
  }),
  overrideExisting: false,
});

export { injectedRtkApi as citywebApi };

...

Update the query call to pass in the array of device ids.

const { 
  data: deviceRecord, 
} = useDeviceRecordListQuery(data.map(({ ID }) => ID), {
  selectFromResult: result => ({
    ...result,
    data: result.data?.map(({ x, y }) => ({ x, y }))
  }),
  skip: !chartData 
});