Skip to content

Commit

Permalink
Merge pull request #39 from lifeomic/get-cached-queries
Browse files Browse the repository at this point in the history
feat: add getQueriesData
  • Loading branch information
swain authored Nov 9, 2023
2 parents d30ba62 + 3001cfd commit 6a44c82
Show file tree
Hide file tree
Showing 5 changed files with 152 additions and 12 deletions.
31 changes: 27 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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' },
Expand All @@ -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).
Expand Down
22 changes: 20 additions & 2 deletions src/cache.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,9 +83,27 @@ export const createCacheUtils = <Endpoints extends RoughEndpoints>(
},
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,
})),
};
};
6 changes: 6 additions & 0 deletions src/combination.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ type DataOfQuery<Query> = Query extends QueryObserverResult<infer Data>
export type CombinedQueriesDefinedResult<
Queries extends QueryObserverResult[],
> = {
status: 'success';
isLoading: false;
isError: false;
data: {
Expand All @@ -32,6 +33,7 @@ export type CombinedQueriesDefinedResult<
export type CombinedQueriesLoadingResult<
Queries extends QueryObserverResult[],
> = {
status: 'loading';
isLoading: true;
isError: false;
data: undefined;
Expand All @@ -40,6 +42,7 @@ export type CombinedQueriesLoadingResult<

export type CombinedQueriesErrorResult<Queries extends QueryObserverResult[]> =
{
status: 'error';
isLoading: false;
isError: true;
data: undefined;
Expand Down Expand Up @@ -70,6 +73,7 @@ export const combineQueries = <Queries extends QueryObserverResult[]>(
if (queries.some((query) => query.status === 'error')) {
return {
...base,
status: 'error',
isLoading: false,
data: undefined,
isError: true,
Expand All @@ -80,6 +84,7 @@ export const combineQueries = <Queries extends QueryObserverResult[]>(
if (queries.some((query) => query.status === 'loading')) {
return {
...base,
status: 'loading',
isLoading: true,
data: undefined,
isError: false,
Expand All @@ -89,6 +94,7 @@ export const combineQueries = <Queries extends QueryObserverResult[]>(

return {
...base,
status: 'success',
isLoading: false,
data: queries.map((query) => query.data) as any,
isError: false,
Expand Down
87 changes: 83 additions & 4 deletions src/hooks.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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' },
Expand All @@ -1149,15 +1149,15 @@ test('getCacheData', async () => {

return (
<>
{cache.getCacheData('GET /items', { filter: 'test-filter' })?.message}
{cache.getQueryData('GET /items', { filter: 'test-filter' })?.message}
</>
);
});

screen2.getByText('test-message');
});

test('getInfiniteCacheData', async () => {
test('getInfiniteQueryData', async () => {
network.mock('GET /list', {
status: 200,
data: { items: [{ message: 'one' }, { message: 'two' }] },
Expand All @@ -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',
});

Expand All @@ -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');
});
18 changes: 16 additions & 2 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -135,15 +135,29 @@ export type CacheUtils<Endpoints extends RoughEndpoints> = {
updater: CacheUpdate<InfiniteData<Endpoints[Route]['Response']>>,
) => void;

getCacheData: <Route extends keyof Endpoints & string>(
getQueryData: <Route extends keyof Endpoints & string>(
route: Route,
payload: RequestPayloadOf<Endpoints, Route>,
) => Endpoints[Route]['Response'] | undefined;

getInfiniteCacheData: <Route extends keyof Endpoints & string>(
getInfiniteQueryData: <Route extends keyof Endpoints & string>(
route: Route,
payload: RequestPayloadOf<Endpoints, Route>,
) => InfiniteData<Endpoints[Route]['Response']> | undefined;

getQueriesData: <Route extends keyof Endpoints & string>(
route: Route,
) => {
payload: RequestPayloadOf<Endpoints, Route>;
data: Endpoints[Route]['Response'] | undefined;
}[];

getInfiniteQueriesData: <Route extends keyof Endpoints & string>(
route: Route,
) => {
payload: RequestPayloadOf<Endpoints, Route>;
data: InfiniteData<Endpoints[Route]['Response']> | undefined;
}[];
};

export type APIQueryHooks<Endpoints extends RoughEndpoints> = {
Expand Down

0 comments on commit 6a44c82

Please sign in to comment.