Last active
January 25, 2023 01:26
-
-
Save aparx/195f30f442ece872feecb019da11fbc1 to your computer and use it in GitHub Desktop.
Enables individual page data remapping and removal for a set of queries using react-query (only through useInfiniteQuery)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import {Query, QueryCache, QueryClient, QueryFilters} from "@tanstack/react-query"; | |
import {InfiniteData} from "@tanstack/query-core/build/lib/types"; | |
export type InfiniteQueryData<U, V> = InfiniteData<U | V | undefined>; | |
export type InfinitePageMapperParam<OldPageT, NewPageT> = { | |
page: OldPageT | NewPageT, | |
index: number, | |
param?: unknown, | |
query?: Query<unknown, unknown, InfiniteQueryData<OldPageT, NewPageT>>, | |
}; | |
export type InfinitePageMapper<OldPageT, NewPageT> = ( | |
data: InfinitePageMapperParam<OldPageT, NewPageT> | |
) => OldPageT | NewPageT | undefined; | |
export type InfiniteQueryArray<OldPageT, NewPageT> | |
= Array<Query<unknown, unknown, InfiniteQueryData<OldPageT, NewPageT>>>; | |
export function ensureQueryHasInfiniteData( | |
query: Query<unknown, unknown, any> | |
): query is Query<unknown, unknown, InfiniteData<any>> { | |
const {data}: any = query.state; | |
return "pages" in data && "pageParams" in data; | |
} | |
export const updateInfinitePagesInClient = <OldPageT = any, NewPageT = OldPageT>( | |
queryClient: QueryClient, | |
mapper: InfinitePageMapper<OldPageT, NewPageT>, | |
filters?: QueryFilters | |
) => updateInfinitePagesInCache(queryClient.getQueryCache(), mapper, filters); | |
export const updateInfinitePagesInCache = <OldPageT = any, NewPageT = OldPageT>( | |
queryCache: QueryCache, | |
mapper: InfinitePageMapper<OldPageT, NewPageT>, | |
filters?: QueryFilters | |
) => updateInfinitePages( | |
queryCache.findAll({ | |
...filters, | |
predicate: (query) => { | |
if (!ensureQueryHasInfiniteData(query)) | |
return false; | |
if (filters && filters.predicate) | |
return filters.predicate(query); | |
return true; | |
}, | |
}) as InfiniteQueryArray<OldPageT, NewPageT>, | |
mapper | |
); | |
export const updateInfinitePages = <OldPageT = any, NewPageT = OldPageT>( | |
queries: InfiniteQueryArray<OldPageT, NewPageT>, | |
mapper: InfinitePageMapper<OldPageT, NewPageT> | |
) => queries.forEach(query => { | |
const data = query.state.data; | |
if (!data) return; // data is invalid, skip | |
const newPages: (OldPageT | NewPageT | undefined)[] = []; | |
const newParams: unknown[] = []; | |
data.pages.forEach((page, index) => { | |
if (page == null) return; | |
const param = data.pageParams?.[index]; | |
const newPage = mapper({ | |
page, index, param, query | |
}); | |
if (!newPage) return; | |
newPages.push(newPage); | |
newParams.push(param); | |
}); | |
query.setData({ | |
pages: newPages, | |
pageParams: newParams | |
} satisfies InfiniteQueryData<OldPageT, NewPageT>); | |
}); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
The problem
Sometimes you may want to undo (or reverse) the fetching of pages with the
useInfiniteQuery
hook without resetting all fetched queries, since that would require a refetch everytime an undone page should be displayed again.I have yet to find a better solution than what is done in this little gist. Read more about why default methods may not work below in the Examples.
Usages and definitions
Function
updateInfinitePages
Used to map the pages in the specified Query array.
Function
updateInfinitePagesInCache
Used to map all pages whose queries' data (provided by given cache) are an instance of InfiniteData.
This is primarily done with an additional filter, besides the optional filter passed to the function, that only exposes queries to the mapper whose data object have the required properties of type InfiniteData (which is used as the query data type by default in react-query)
Function
updateInfinitePagesInClient
Invokes updateInfinitePagesInCache and passes the client's query cache as the cache to provide the queries.
Examples
Assume we create an
useInfiniteQuery
hook with a button ("Show more") that when clicked loads more elements using any arbitrary Rest-API that then queries our backend database. There's an additional button ("Show less") that is shown whenever there is more than one page fetched. The "Show less" button will remove all pages except the first one.Default approach
The approach that comes to mind at first would be to simply reset and/or invalidate the cache, which would force a refetch from react-query for our first page. But this is unnecessary, since we already have our data handy. Thus, the other approach is what we seek.
The other approach
Same assumption as above, but we use following method on our "Show less" button, when we click it.
Now, whenever we click our "Show less" button it simply just remaps the page content for each page to the value returned by our mapper (the closure we pass). This mapper implementation states that whenever our iteration
index
is zero (which always represents the first page) we actually do not change the page content, but if theindex
is not zero we remove our page (including the param) entirely from the query's data.