EntityStore: add support for X-Total-Count from the API

This commit is contained in:
Alex Gleason 2023-03-22 17:39:58 -05:00
parent 1eed61c386
commit a256665aad
No known key found for this signature in database
GPG Key ID: 7211D1F99744FBB7
5 changed files with 28 additions and 7 deletions

View File

@ -42,7 +42,8 @@ test('import entities into a list', () => {
const cache = result.TestEntity as EntityCache<TestEntity>; const cache = result.TestEntity as EntityCache<TestEntity>;
expect(cache.store['2']!.msg).toBe('benis'); expect(cache.store['2']!.msg).toBe('benis');
expect(cache.lists.thingies?.ids.size).toBe(3); expect(cache.lists.thingies!.ids.size).toBe(3);
expect(cache.lists.thingies!.state.totalCount).toBe(3);
// Now try adding an additional item. // Now try adding an additional item.
const entities2: TestEntity[] = [ const entities2: TestEntity[] = [
@ -54,7 +55,8 @@ test('import entities into a list', () => {
const cache2 = result2.TestEntity as EntityCache<TestEntity>; const cache2 = result2.TestEntity as EntityCache<TestEntity>;
expect(cache2.store['4']!.msg).toBe('hehe'); expect(cache2.store['4']!.msg).toBe('hehe');
expect(cache2.lists.thingies?.ids.size).toBe(4); expect(cache2.lists.thingies!.ids.size).toBe(4);
expect(cache2.lists.thingies!.state.totalCount).toBe(4);
// Finally, update an item. // Finally, update an item.
const entities3: TestEntity[] = [ const entities3: TestEntity[] = [
@ -66,7 +68,8 @@ test('import entities into a list', () => {
const cache3 = result3.TestEntity as EntityCache<TestEntity>; const cache3 = result3.TestEntity as EntityCache<TestEntity>;
expect(cache3.store['2']!.msg).toBe('yolofam'); expect(cache3.store['2']!.msg).toBe('yolofam');
expect(cache3.lists.thingies?.ids.size).toBe(4); // unchanged expect(cache3.lists.thingies!.ids.size).toBe(4); // unchanged
expect(cache3.lists.thingies!.state.totalCount).toBe(4);
}); });
test('fetching updates the list state', () => { test('fetching updates the list state', () => {
@ -92,7 +95,7 @@ test('deleting items', () => {
lists: { lists: {
'': { '': {
ids: new Set(['1', '2', '3']), ids: new Set(['1', '2', '3']),
state: createListState(), state: { ...createListState(), totalCount: 3 },
}, },
}, },
}, },
@ -103,6 +106,7 @@ test('deleting items', () => {
expect(result.TestEntity!.store).toMatchObject({ '2': { id: '2' } }); expect(result.TestEntity!.store).toMatchObject({ '2': { id: '2' } });
expect([...result.TestEntity!.lists['']!.ids]).toEqual(['2']); expect([...result.TestEntity!.lists['']!.ids]).toEqual(['2']);
expect(result.TestEntity!.lists['']!.state.totalCount).toBe(1);
}); });
test('dismiss items', () => { test('dismiss items', () => {
@ -112,7 +116,7 @@ test('dismiss items', () => {
lists: { lists: {
'yolo': { 'yolo': {
ids: new Set(['1', '2', '3']), ids: new Set(['1', '2', '3']),
state: createListState(), state: { ...createListState(), totalCount: 3 },
}, },
}, },
}, },
@ -123,4 +127,5 @@ test('dismiss items', () => {
expect(result.TestEntity!.store).toMatchObject(state.TestEntity!.store); expect(result.TestEntity!.store).toMatchObject(state.TestEntity!.store);
expect([...result.TestEntity!.lists.yolo!.ids]).toEqual(['2']); expect([...result.TestEntity!.lists.yolo!.ids]).toEqual(['2']);
expect(result.TestEntity!.lists.yolo!.state.totalCount).toBe(1);
}); });

View File

@ -47,6 +47,7 @@ function useEntities<TEntity extends Entity>(
const lastFetchedAt = useListState(path, 'lastFetchedAt'); const lastFetchedAt = useListState(path, 'lastFetchedAt');
const isFetched = useListState(path, 'fetched'); const isFetched = useListState(path, 'fetched');
const isError = !!useListState(path, 'error'); const isError = !!useListState(path, 'error');
const totalCount = useListState(path, 'totalCount');
const next = useListState(path, 'next'); const next = useListState(path, 'next');
const prev = useListState(path, 'prev'); const prev = useListState(path, 'prev');
@ -61,10 +62,12 @@ function useEntities<TEntity extends Entity>(
const response = await api.get(url); const response = await api.get(url);
const schema = opts.schema || z.custom<TEntity>(); const schema = opts.schema || z.custom<TEntity>();
const entities = filteredArray(schema).parse(response.data); const entities = filteredArray(schema).parse(response.data);
const numItems = (selectList(getState(), path)?.ids.size || 0) + entities.length;
dispatch(entitiesFetchSuccess(entities, entityType, listKey, { dispatch(entitiesFetchSuccess(entities, entityType, listKey, {
next: getNextLink(response), next: getNextLink(response),
prev: getPrevLink(response), prev: getPrevLink(response),
totalCount: Number(response.headers['x-total-count'] ?? numItems) || 0,
fetching: false, fetching: false,
fetched: true, fetched: true,
error: null, error: null,
@ -108,6 +111,7 @@ function useEntities<TEntity extends Entity>(
fetchPreviousPage, fetchPreviousPage,
hasNextPage: !!next, hasNextPage: !!next,
hasPreviousPage: !!prev, hasPreviousPage: !!prev,
totalCount,
isError, isError,
isFetched, isFetched,
isFetching, isFetching,

View File

@ -60,7 +60,10 @@ const deleteEntities = (
if (!opts?.preserveLists) { if (!opts?.preserveLists) {
for (const list of Object.values(cache.lists)) { for (const list of Object.values(cache.lists)) {
list?.ids.delete(id); if (list) {
list.ids.delete(id);
list.state.totalCount--;
}
} }
} }
} }
@ -82,6 +85,7 @@ const dismissEntities = (
if (list) { if (list) {
for (const id of ids) { for (const id of ids) {
list.ids.delete(id); list.ids.delete(id);
list.state.totalCount--;
} }
draft[entityType] = cache; draft[entityType] = cache;

View File

@ -23,6 +23,8 @@ interface EntityListState {
next: string | undefined next: string | undefined
/** Previous URL for pagination, if any. */ /** Previous URL for pagination, if any. */
prev: string | undefined prev: string | undefined
/** Total number of items according to the API. */
totalCount: number
/** Error returned from the API, if any. */ /** Error returned from the API, if any. */
error: any error: any
/** Whether data has already been fetched */ /** Whether data has already been fetched */

View File

@ -11,9 +11,14 @@ const updateStore = (store: EntityStore, entities: Entity[]): EntityStore => {
/** Update the list with new entity IDs. */ /** Update the list with new entity IDs. */
const updateList = (list: EntityList, entities: Entity[]): EntityList => { const updateList = (list: EntityList, entities: Entity[]): EntityList => {
const newIds = entities.map(entity => entity.id); const newIds = entities.map(entity => entity.id);
const ids = new Set([...Array.from(list.ids), ...newIds]);
const sizeDiff = ids.size - list.ids.size;
list.state.totalCount += sizeDiff;
return { return {
...list, ...list,
ids: new Set([...Array.from(list.ids), ...newIds]), ids,
}; };
}; };
@ -33,6 +38,7 @@ const createList = (): EntityList => ({
const createListState = (): EntityListState => ({ const createListState = (): EntityListState => ({
next: undefined, next: undefined,
prev: undefined, prev: undefined,
totalCount: 0,
error: null, error: null,
fetched: false, fetched: false,
fetching: false, fetching: false,