From 3d7cccd984b8d575e54b0dd5cc9db0775c47fec8 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Tue, 11 Apr 2023 10:04:31 -0500 Subject: [PATCH] EntityStore: allow customizing import position --- app/soapbox/entity-store/__tests__/reducer.test.ts | 2 +- app/soapbox/entity-store/actions.ts | 7 +++++-- app/soapbox/entity-store/hooks/useCreateEntity.ts | 2 +- app/soapbox/entity-store/hooks/useEntities.ts | 10 +++++----- app/soapbox/entity-store/reducer.ts | 9 +++++---- app/soapbox/entity-store/types.ts | 4 ++++ app/soapbox/entity-store/utils.ts | 7 ++++--- 7 files changed, 25 insertions(+), 16 deletions(-) diff --git a/app/soapbox/entity-store/__tests__/reducer.test.ts b/app/soapbox/entity-store/__tests__/reducer.test.ts index 1cfc19697..3e6aa3510 100644 --- a/app/soapbox/entity-store/__tests__/reducer.test.ts +++ b/app/soapbox/entity-store/__tests__/reducer.test.ts @@ -110,7 +110,7 @@ test('import entities with override', () => { const now = new Date(); - const action = entitiesFetchSuccess(entities, 'TestEntity', 'thingies', { + const action = entitiesFetchSuccess(entities, 'TestEntity', 'thingies', 'end', { next: undefined, prev: undefined, totalCount: 2, diff --git a/app/soapbox/entity-store/actions.ts b/app/soapbox/entity-store/actions.ts index c3ba25559..bb96255c6 100644 --- a/app/soapbox/entity-store/actions.ts +++ b/app/soapbox/entity-store/actions.ts @@ -1,4 +1,4 @@ -import type { Entity, EntityListState } from './types'; +import type { Entity, EntityListState, ImportPosition } from './types'; const ENTITIES_IMPORT = 'ENTITIES_IMPORT' as const; const ENTITIES_DELETE = 'ENTITIES_DELETE' as const; @@ -10,12 +10,13 @@ const ENTITIES_FETCH_FAIL = 'ENTITIES_FETCH_FAIL' as const; const ENTITIES_INVALIDATE_LIST = 'ENTITIES_INVALIDATE_LIST' as const; /** Action to import entities into the cache. */ -function importEntities(entities: Entity[], entityType: string, listKey?: string) { +function importEntities(entities: Entity[], entityType: string, listKey?: string, pos?: ImportPosition) { return { type: ENTITIES_IMPORT, entityType, entities, listKey, + pos, }; } @@ -62,6 +63,7 @@ function entitiesFetchSuccess( entities: Entity[], entityType: string, listKey?: string, + pos?: ImportPosition, newState?: EntityListState, overwrite = false, ) { @@ -70,6 +72,7 @@ function entitiesFetchSuccess( entityType, entities, listKey, + pos, newState, overwrite, }; diff --git a/app/soapbox/entity-store/hooks/useCreateEntity.ts b/app/soapbox/entity-store/hooks/useCreateEntity.ts index 31299344e..6b4aaff73 100644 --- a/app/soapbox/entity-store/hooks/useCreateEntity.ts +++ b/app/soapbox/entity-store/hooks/useCreateEntity.ts @@ -30,7 +30,7 @@ function useCreateEntity( const entity = schema.parse(result.data); // TODO: optimistic updating - dispatch(importEntities([entity], entityType, listKey)); + dispatch(importEntities([entity], entityType, listKey, 'start')); if (callbacks.onSuccess) { callbacks.onSuccess(entity); diff --git a/app/soapbox/entity-store/hooks/useEntities.ts b/app/soapbox/entity-store/hooks/useEntities.ts index a99ce8631..cd413f487 100644 --- a/app/soapbox/entity-store/hooks/useEntities.ts +++ b/app/soapbox/entity-store/hooks/useEntities.ts @@ -54,7 +54,7 @@ function useEntities( const next = useListState(path, 'next'); const prev = useListState(path, 'prev'); - const fetchPage = async(req: EntityFn, overwrite = false): Promise => { + const fetchPage = async(req: EntityFn, pos: 'start' | 'end', overwrite = false): Promise => { // Get `isFetching` state from the store again to prevent race conditions. const isFetching = selectListState(getState(), path, 'fetching'); if (isFetching) return; @@ -67,7 +67,7 @@ function useEntities( const parsedCount = realNumberSchema.safeParse(response.headers['x-total-count']); const totalCount = parsedCount.success ? parsedCount.data : undefined; - dispatch(entitiesFetchSuccess(entities, entityType, listKey, { + dispatch(entitiesFetchSuccess(entities, entityType, listKey, pos, { next: getNextLink(response), prev: getPrevLink(response), totalCount: Number(totalCount) >= entities.length ? totalCount : undefined, @@ -83,18 +83,18 @@ function useEntities( }; const fetchEntities = async(): Promise => { - await fetchPage(entityFn, true); + await fetchPage(entityFn, 'end', true); }; const fetchNextPage = async(): Promise => { if (next) { - await fetchPage(() => api.get(next)); + await fetchPage(() => api.get(next), 'end'); } }; const fetchPreviousPage = async(): Promise => { if (prev) { - await fetchPage(() => api.get(prev)); + await fetchPage(() => api.get(prev), 'start'); } }; diff --git a/app/soapbox/entity-store/reducer.ts b/app/soapbox/entity-store/reducer.ts index b71fb812f..ef7b604d9 100644 --- a/app/soapbox/entity-store/reducer.ts +++ b/app/soapbox/entity-store/reducer.ts @@ -14,7 +14,7 @@ import { import { createCache, createList, updateStore, updateList } from './utils'; import type { DeleteEntitiesOpts } from './actions'; -import type { Entity, EntityCache, EntityListState } from './types'; +import type { Entity, EntityCache, EntityListState, ImportPosition } from './types'; enableMapSet(); @@ -29,6 +29,7 @@ const importEntities = ( entityType: string, entities: Entity[], listKey?: string, + pos?: ImportPosition, newState?: EntityListState, overwrite = false, ): State => { @@ -43,7 +44,7 @@ const importEntities = ( list.ids = new Set(); } - list = updateList(list, entities); + list = updateList(list, entities, pos); if (newState) { list.state = newState; @@ -159,7 +160,7 @@ const invalidateEntityList = (state: State, entityType: string, listKey: string) function reducer(state: Readonly = {}, action: EntityAction): State { switch (action.type) { case ENTITIES_IMPORT: - return importEntities(state, action.entityType, action.entities, action.listKey); + return importEntities(state, action.entityType, action.entities, action.listKey, action.pos); case ENTITIES_DELETE: return deleteEntities(state, action.entityType, action.ids, action.opts); case ENTITIES_DISMISS: @@ -167,7 +168,7 @@ function reducer(state: Readonly = {}, action: EntityAction): State { case ENTITIES_INCREMENT: return incrementEntities(state, action.entityType, action.listKey, action.diff); case ENTITIES_FETCH_SUCCESS: - return importEntities(state, action.entityType, action.entities, action.listKey, action.newState, action.overwrite); + return importEntities(state, action.entityType, action.entities, action.listKey, action.pos, action.newState, action.overwrite); case ENTITIES_FETCH_REQUEST: return setFetching(state, action.entityType, action.listKey, true); case ENTITIES_FETCH_FAIL: diff --git a/app/soapbox/entity-store/types.ts b/app/soapbox/entity-store/types.ts index 006b13ba2..5fff2f474 100644 --- a/app/soapbox/entity-store/types.ts +++ b/app/soapbox/entity-store/types.ts @@ -47,10 +47,14 @@ interface EntityCache { } } +/** Whether to import items at the start or end of the list. */ +type ImportPosition = 'start' | 'end' + export { Entity, EntityStore, EntityList, EntityListState, EntityCache, + ImportPosition, }; \ No newline at end of file diff --git a/app/soapbox/entity-store/utils.ts b/app/soapbox/entity-store/utils.ts index 3f65f1ee0..58d54465a 100644 --- a/app/soapbox/entity-store/utils.ts +++ b/app/soapbox/entity-store/utils.ts @@ -1,4 +1,4 @@ -import type { Entity, EntityStore, EntityList, EntityCache, EntityListState } from './types'; +import type { Entity, EntityStore, EntityList, EntityCache, EntityListState, ImportPosition } from './types'; /** Insert the entities into the store. */ const updateStore = (store: EntityStore, entities: Entity[]): EntityStore => { @@ -9,9 +9,10 @@ const updateStore = (store: EntityStore, entities: Entity[]): EntityStore => { }; /** Update the list with new entity IDs. */ -const updateList = (list: EntityList, entities: Entity[]): EntityList => { +const updateList = (list: EntityList, entities: Entity[], pos: ImportPosition = 'end'): EntityList => { const newIds = entities.map(entity => entity.id); - const ids = new Set([...newIds, ...Array.from(list.ids)]); + const oldIds = Array.from(list.ids); + const ids = new Set(pos === 'start' ? [...newIds, ...oldIds] : [...oldIds, ...newIds]); if (typeof list.state.totalCount === 'number') { const sizeDiff = ids.size - list.ids.size;