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

3

20

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

Anaphase answered 9/6, 2021 at 17:49 Comment(4)
The createApi method is part of our new "RTK Query" API in Redux Toolkit 1.6, just released this week: redux-toolkit.js.org/rtk-query/overviewPyrognostics
@Anaphase : can you give some further examples of what you're trying to accomplish? What specific data are you looking to get? What do you mean by "state of all the queries", specifically?Pyrognostics
@Pyrognostics I'm trying to implement pagination based on scroll, so once the user scrolls to the bottom we need to fetch the next page, still maintaining the previous page data, using the generated hook, we get the data of the current page only, so wanted to know if there's a way to access the data of the previous queries.Anaphase
Off the top of my head, I'm not sure that's a use case we support atm. Each endpoint+params combination gets fetched and cached separately, so if you went from useResourceQuery(1) to useResourceQuery(2), that's two separate response datasets. Might be worth filing a feature request issue.Pyrognostics
T
14

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.

Tade answered 9/6, 2021 at 20:47 Comment(0)
P
9

Since RTK v1.9 you can now use the merge option to merge the data of multiple queries. This is especially useful for things like infinite scrolling. Please check the answer I posted on another question for more information.

createApi({
  baseQuery: fetchBaseQuery({ baseUrl: '/' }),
  endpoints: (build) => ({
    listItems: build.query<string[], number>({
      query: (pageNumber) => `/listItems?page=${pageNumber}`,
      // Only have one cache entry because the arg always maps to one string
      serializeQueryArgs: ({ endpointName }) => {
        return endpointName
      },
      // Always merge incoming data to the cache entry
      merge: (currentCache, newItems) => {
        currentCache.push(...newItems)
      },
      // Refetch when the page arg changes
      forceRefetch({ currentArg, previousArg }) {
        return currentArg !== previousArg
      },
    }),
  }),
})
Phallicism answered 19/12, 2022 at 13:42 Comment(0)
R
6

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[];
}
Rudich answered 20/2, 2022 at 17:18 Comment(2)
do you have a working example on this?Chillon
github.com/HassanHeydariNasab/infinite-scrolling-rtk @GukuRudich

© 2022 - 2024 — McMap. All rights reserved.