Is there any way to fetch all the responses stored in api slice [RTK Query]?

Viewed 6253

Here's the hook which returns the data based on the page

    const {
        data,
        isFetching,
        isLoading,
        isError,
    } = useGetResourceQuery(page, perPage );

here's the api

    export const api = createApi({
        baseQuery: fetchBaseQuery({
            baseUrl: "http://localhost:3001",
        
        }),
        tagTypes: ["resource"],
        endpoints: (build) => ({
            getResource: build.query({
            query: (page = 1, perPage = 20) =>
                `resource?spage=${page}&limit=${perPage}`,
            }),
        }),
    });
    export const {useGetResourceQuery} = api

Is there any way we can extract the state of all the queries? I want to implement pagination based on scroll (infinite scroll) Do we need to update the middleware? or are there any ways to access the state and expose it as a function above, I went through the documentation but couldn't find a solution

I can use local state and concatenate them but wanted to know if it's possible using RTK

Thanks

2 Answers

I think most implementations overcomplicate the problem of "infinite scroll" by a lot. You can achieve this by stepping a bit back and thinking about what you really need:

  • the current in-view data
  • a bit extra data into the direction we're scrolling to

Since we are lazy and do not want to track which direction we're scrolling to, just assume

  • the current in-view data
  • a bit extra data into both directions.

Also let's assume the query returns a response like

  {
    offset: 50,
    items: [...]
  }

So assuming one page is big enough to contain all the data for a screen, we'd end up with something like

const currentPage = // something calculated from ScrollPosition

const lastResult = usePageQuery(currentPage - 1, { skip: currentPage === 1 }) // don't fetch pages before 0
const currentResult = usePageQuery(currentPage)
const nextResult = usePageQuery(currentPage + 1)

const combined = useMemo(() => {
  const arr = new Array(pageSize * (currentPage + 1))
  for (const data of [lastResult.data, currentResult.data, nextResult.data]) {
    if (data) {
      arr.splice(data.offset, data.items.length, ...data.items)
    }
  }
  return arr
}, [pageSize, currentPage, lastResult.data, currentResult.data, nextResult.data])

// work with `combined` here

Since requests that are not referenced by your component, will stay in the store for 60 seconds, the user can scroll up quickly through many pages without further requests - but also, data that will probably never scrolled to again will be removed from the cache and simply re-fetched when necessary.

You can use queryFn to assemble the cached results you already have. remember to use invalidation to get the updated state.

In the following code, the first queryFn is just a fake API, and the second endpoint is the solution you are looking for:

export const songsApi = createApi({
  reducerPath: "songsApi",
  baseQuery: fetchBaseQuery(),
  endpoints: (builder) => ({
    getSongs: builder.query<Result<Song>, { offset: number; limit: number }>({
      queryFn: ({ offset, limit }) => {
        let list: Song[] = [];
        for (let i = offset; i < offset + limit; i++) {
          list.push({ id: i.toString(), name: `Song ${i}` });
        }
        return new Promise((resolve) => {
          setTimeout(() => {
            resolve({
              data: {
                total: 100,
                list
              } as Result<Song>
            });
          }, 1000);
        });
      }
    }),
 
    getAllSongs: builder.query<Result<Song>, void>({
      queryFn: (_, { getState }) => {
        let state = getState();
        let songs: Song[] = [];
        for (let offset = 0; offset < 100; offset += 10) {
          const { data } = songsApi.endpoints.getSongs.select({
            offset,
            limit: 10
          })(state as any);
          if (data && data.list) {
            songs.push(...data!.list);
          }
        }
        return Promise.resolve({ data: { total: 100, list: songs as Song[] } });
      }
    })
  })
});

interface Result<T> {
  total: number;
  list: T[];
}
Related