diff --git a/README.md b/README.md index 132b2a3..d8e3ff3 100644 --- a/README.md +++ b/README.md @@ -484,14 +484,14 @@ cache.updateInfiniteCache( ); ``` -#### `getCacheData` +#### `getQueryData` Get the cached data for a query, if there is any. ```typescript const cache = useAPICache(); -const value = cache.getCacheData( +const value = cache.getQueryData( // Specify the route + payload that you'd like to get the cached value for. 'GET /messages', { filter: 'some-filter' }, @@ -500,18 +500,41 @@ const value = cache.getCacheData( value; // Message[] | undefined ``` -When dealing with a cache entry that was initiated via `useInfiniteAPIQuery` (paginated) prefer using `getInfiniteCacheData` which otherwise behaves the same as `getCacheData`. +When dealing with a cache entry that was initiated via `useInfiniteAPIQuery` (paginated) prefer using `getInfiniteQueryData` which otherwise behaves the same as `getQueryData`. ```typescript const cache = useAPICache(); -const value = cache.getInfiniteCacheData('GET /messages', { +const value = cache.getInfiniteQueryData('GET /messages', { filter: 'some-filter', }); value; // { pages: Message[]; } ``` +#### `getQueriesData` + +Get the cached data for every query of the provided route, by payload. + +```typescript +const cache = useAPICache(); + +// Specify the route. +const value = cache.getQueriesData('GET /messages'); + +value; // { payload: { filter: string; }; data: Message[] | undefined; }[] +``` + +If you want to fetch cache data created by `useInfiniteAPIQuery`, prefer using `getInfiniteQueriesData` which otherwise behaves the same as `getQueriesData`. + +```typescript +const cache = useAPICache(); + +const value = cache.getInfiniteQueriesData('GET /messages'); + +value; // { payload: { filter: string; }; data: { items: } | undefined; } +``` + ## Test Utility API Reference `one-query` also provides a testing utility for doing type-safe mocking of API endpoints in tests. This utility is powered by [`msw`](https://github.com/mswjs/msw). diff --git a/src/cache.ts b/src/cache.ts index 6334717..951fff5 100644 --- a/src/cache.ts +++ b/src/cache.ts @@ -83,9 +83,27 @@ export const createCacheUtils = ( }, updateCache: updateCache(), updateInfiniteCache: updateCache(INFINITE_QUERY_KEY), - getCacheData: (route, payload) => + getQueryData: (route, payload) => client.getQueryData([makeQueryKey(route, payload)]), - getInfiniteCacheData: (route, payload) => + getInfiniteQueryData: (route, payload) => client.getQueryData([INFINITE_QUERY_KEY, makeQueryKey(route, payload)]), + getQueriesData: (route) => + client + .getQueriesData(createQueryFilterFromSpec({ [route]: 'all' })) + // Don't match infinite queries + .filter(([queryKey]) => queryKey[0] !== INFINITE_QUERY_KEY) + .map(([queryKey, data]) => ({ + payload: (queryKey[0] as any).payload, + data, + })), + getInfiniteQueriesData: (route) => + client + .getQueriesData(createQueryFilterFromSpec({ [route]: 'all' })) + // Only match infinite queries + .filter(([queryKey]) => queryKey[0] === INFINITE_QUERY_KEY) + .map(([queryKey, data]) => ({ + payload: (queryKey[1] as any).payload, + data: data as any, + })), }; }; diff --git a/src/combination.ts b/src/combination.ts index 696b5c4..5491ad2 100644 --- a/src/combination.ts +++ b/src/combination.ts @@ -17,6 +17,7 @@ type DataOfQuery = Query extends QueryObserverResult export type CombinedQueriesDefinedResult< Queries extends QueryObserverResult[], > = { + status: 'success'; isLoading: false; isError: false; data: { @@ -32,6 +33,7 @@ export type CombinedQueriesDefinedResult< export type CombinedQueriesLoadingResult< Queries extends QueryObserverResult[], > = { + status: 'loading'; isLoading: true; isError: false; data: undefined; @@ -40,6 +42,7 @@ export type CombinedQueriesLoadingResult< export type CombinedQueriesErrorResult = { + status: 'error'; isLoading: false; isError: true; data: undefined; @@ -70,6 +73,7 @@ export const combineQueries = ( if (queries.some((query) => query.status === 'error')) { return { ...base, + status: 'error', isLoading: false, data: undefined, isError: true, @@ -80,6 +84,7 @@ export const combineQueries = ( if (queries.some((query) => query.status === 'loading')) { return { ...base, + status: 'loading', isLoading: true, data: undefined, isError: false, @@ -89,6 +94,7 @@ export const combineQueries = ( return { ...base, + status: 'success', isLoading: false, data: queries.map((query) => query.data) as any, isError: false, diff --git a/src/hooks.test.tsx b/src/hooks.test.tsx index 38111a6..42e86b5 100644 --- a/src/hooks.test.tsx +++ b/src/hooks.test.tsx @@ -1124,7 +1124,7 @@ test('passing a function for `client` is supported', async () => { }); }); -test('getCacheData', async () => { +test('getQueryData', async () => { network.mock('GET /items', { status: 200, data: { message: 'test-message' }, @@ -1149,7 +1149,7 @@ test('getCacheData', async () => { return ( <> - {cache.getCacheData('GET /items', { filter: 'test-filter' })?.message} + {cache.getQueryData('GET /items', { filter: 'test-filter' })?.message} ); }); @@ -1157,7 +1157,7 @@ test('getCacheData', async () => { screen2.getByText('test-message'); }); -test('getInfiniteCacheData', async () => { +test('getInfiniteQueryData', async () => { network.mock('GET /list', { status: 200, data: { items: [{ message: 'one' }, { message: 'two' }] }, @@ -1180,7 +1180,7 @@ test('getInfiniteCacheData', async () => { const screen2 = render(() => { const cache = useAPICache(); - const value = cache.getInfiniteCacheData('GET /list', { + const value = cache.getInfiniteQueryData('GET /list', { filter: 'test-filter', }); @@ -1191,3 +1191,82 @@ test('getInfiniteCacheData', async () => { screen2.getByText('one,two'); }); + +test('getQueriesData', async () => { + network.mock('GET /items', ({ query }) => ({ + status: 200, + data: { message: query.filter }, + })); + + // Populate the cache. + const screen1 = render(() => { + const query = useCombinedAPIQueries( + ['GET /items', { filter: 'test-filter' }], + ['GET /items', { filter: 'other-filter' }], + ); + return <>{query.status}; + }); + + await TestingLibrary.waitFor(() => { + screen1.getByText('success'); + }); + + screen1.unmount(); + + // Now, fetch from the cache. + + const screen2 = render(() => { + const cache = useAPICache(); + + const value = cache + .getQueriesData('GET /items') + .map(({ data }) => data?.message) + .join(','); + + return <>{value}; + }); + + screen2.getByText('test-filter,other-filter'); +}); + +test('getInfiniteQueriesData', async () => { + network.mock('GET /list', ({ query }) => ({ + status: 200, + data: { items: [{ message: query.filter }] }, + })); + + // Populate the cache. + const screen1 = render(() => { + const query1 = useInfiniteAPIQuery('GET /list', { filter: 'test-filter' }); + const query2 = useInfiniteAPIQuery('GET /list', { filter: 'other-filter' }); + + return ( + <> + {query1.status},{query2.status} + + ); + }); + + await TestingLibrary.waitFor(() => { + screen1.getByText('success,success'); + }); + + screen1.unmount(); + + // Now, fetch from the cache. + + const screen2 = render(() => { + const cache = useAPICache(); + + const value = cache.getInfiniteQueriesData('GET /list'); + + const messages = value + .flatMap(({ data }) => data!.pages) + .flatMap((p) => p.items) + .map((i) => i.message); + + return <>{messages?.join(',')}; + }); + + screen2.getByText('test-filter,other-filter'); +}); diff --git a/src/types.ts b/src/types.ts index 1a766bd..25d27a3 100644 --- a/src/types.ts +++ b/src/types.ts @@ -135,15 +135,29 @@ export type CacheUtils = { updater: CacheUpdate>, ) => void; - getCacheData: ( + getQueryData: ( route: Route, payload: RequestPayloadOf, ) => Endpoints[Route]['Response'] | undefined; - getInfiniteCacheData: ( + getInfiniteQueryData: ( route: Route, payload: RequestPayloadOf, ) => InfiniteData | undefined; + + getQueriesData: ( + route: Route, + ) => { + payload: RequestPayloadOf; + data: Endpoints[Route]['Response'] | undefined; + }[]; + + getInfiniteQueriesData: ( + route: Route, + ) => { + payload: RequestPayloadOf; + data: InfiniteData | undefined; + }[]; }; export type APIQueryHooks = {