reduxjs / redux-toolkit

The official, opinionated, batteries-included toolset for efficient Redux development
https://redux-toolkit.js.org
MIT License
10.63k stars 1.15k forks source link

RTK Query: await refetches triggered by `forceRefetch` #2921

Open JacobJaffe opened 1 year ago

JacobJaffe commented 1 year ago

The changes to allow awaiting query.refetch() are really nice, as is the ability now to build paginated flows with merge / forceRefetch / serializeQueryArgs.

Is there a way that promsie-based refetch could work with forceRefetch?

For context, I'm using a pattern very similar to https://github.com/reduxjs/redux-toolkit/issues/2874 where pagination cursor indicates a full-refresh from the head of the list.

Here's a basic example, where an endpoint would take an item as a cursor for the next page, or no cursor for the head:

// api
 ...
    getList: builder.query<
      {
        items: Item[];
        args: {
          cursor?: Item;
        };
      },
      {
        cursor?: Item;
      }
    >({
      query: ({ cursor }) => ({
        endpoint: "itemList",
        payload: {
          cursor,
        },
      }),
      transformResponse: (items: Item[], meta, args) => ({
        items,
        args,
      }),
      serializeQueryArgs: ({ endpointName }) => {
        return endpointName;
      },
      merge: (currentCache, responseData) => {
        if (responseData.args.cursor) {
          currentCache.items.push(...responseData.items);
          currentCache.args = responseData.args;
        } else {
          return responseData;
        }
      },
    }),

// component

const ListComponent = () => {
  const [cursor, setCursor] = useState<Item>();
  const query = useGetListQuery({ cursor });

  const loadMore = () => setCursor(query.data?.items[query.data?.items.length - 1]);
  const refreshList = () => setCursor(undefined);

  return (
    <List 
       isRefreshing = { /* THIS IS THE ISSUE */ }
       data={ query.data?.items ?? [] }
       onEndReached={ loadMore }
       onRefresh={ refreshList }
       ...
     />
  )
};

With a normal query, this is now easily achievable via something like:

const Component = () => {
  const query = useQuery();
  const [isRefreshing, setIsRefreshing] = useState(false);
  const refresh = async () => { 
    setIsRefreshing(true); 
    await query.refetch();
    setIsRreshing(false);
  };

  return <Foo data={query.data} onRefresh={refresh} isRefreshing={isRefreshing} />
}

But since the refresh is triggered by the param change, it feels like it's back to needing a solution like pre-promise-refetch ( like tracking isFetching and trying to ensure the cause of the fetching is known)

...

Conceptually, what I want is trigger a refetch and await it in a single step:

refreshList = async () => {
  setIsRefreshing(true);
  setCursor(undefined);
  await query.refetch();
  setIsRefreshing(false);
}
JacobJaffe commented 1 year ago

Alternatively, is there a way isRefetching could be added to the query, in addition to isLoading and isFetching?

Where isRefetching is true if the query is triggered by either:

  1. query.refetch()
  2. forceRefetch condition of the query definition