From 8f67d2c76fd1efce4d2b8ac9a60b967b24313151 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Wed, 22 Mar 2023 16:06:10 -0500 Subject: [PATCH] EntityStore: consolidate types, fix type of "path" --- app/soapbox/entity-store/hooks/types.ts | 28 ++++++++++++++- .../entity-store/hooks/useCreateEntity.ts | 11 +++--- .../entity-store/hooks/useDismissEntity.ts | 9 +++-- app/soapbox/entity-store/hooks/useEntities.ts | 34 +++++++------------ app/soapbox/entity-store/hooks/useEntity.ts | 4 +-- .../entity-store/hooks/useEntityActions.ts | 8 ++--- app/soapbox/entity-store/hooks/utils.ts | 9 +++++ app/soapbox/hooks/useGroups.ts | 2 +- 8 files changed, 68 insertions(+), 37 deletions(-) create mode 100644 app/soapbox/entity-store/hooks/utils.ts diff --git a/app/soapbox/entity-store/hooks/types.ts b/app/soapbox/entity-store/hooks/types.ts index 89992c12d..7ce99fd82 100644 --- a/app/soapbox/entity-store/hooks/types.ts +++ b/app/soapbox/entity-store/hooks/types.ts @@ -3,4 +3,30 @@ import type z from 'zod'; type EntitySchema = z.ZodType; -export type { EntitySchema }; \ No newline at end of file +/** + * Tells us where to find/store the entity in the cache. + * This value is accepted in hooks, but needs to be parsed into an `EntitiesPath` + * before being passed to the store. + */ +type ExpandedEntitiesPath = [ + /** Name of the entity type for use in the global cache, eg `'Notification'`. */ + entityType: string, + /** + * Name of a particular index of this entity type. + * Multiple params get combined into one string with a `:` separator. + */ + ...listKeys: string[], +] + +/** Used to look up an entity in a list. */ +type EntitiesPath = [entityType: string, listKey: string] + +/** Used to look up a single entity by its ID. */ +type EntityPath = [entityType: string, entityId: string] + +export type { + EntitySchema, + ExpandedEntitiesPath, + EntitiesPath, + EntityPath, +}; \ No newline at end of file diff --git a/app/soapbox/entity-store/hooks/useCreateEntity.ts b/app/soapbox/entity-store/hooks/useCreateEntity.ts index 72873504b..719bed971 100644 --- a/app/soapbox/entity-store/hooks/useCreateEntity.ts +++ b/app/soapbox/entity-store/hooks/useCreateEntity.ts @@ -4,10 +4,11 @@ import { useAppDispatch } from 'soapbox/hooks'; import { importEntities } from '../actions'; -import type { Entity } from '../types'; -import type { EntitySchema } from './types'; +import { parseEntitiesPath } from './utils'; + +import type { Entity } from '../types'; +import type { EntitySchema, ExpandedEntitiesPath } from './types'; -type EntityPath = [entityType: string, listKey?: string] type CreateFn = (params: Params) => Promise | Result; interface UseCreateEntityOpts { @@ -30,11 +31,13 @@ interface EntityCallbacks { } function useCreateEntity( - path: EntityPath, + expandedPath: ExpandedEntitiesPath, createFn: CreateFn, opts: UseCreateEntityOpts = {}, ) { + const path = parseEntitiesPath(expandedPath); const [entityType, listKey] = path; + const dispatch = useAppDispatch(); return async function createEntity( diff --git a/app/soapbox/entity-store/hooks/useDismissEntity.ts b/app/soapbox/entity-store/hooks/useDismissEntity.ts index 65eb8599f..c887e9490 100644 --- a/app/soapbox/entity-store/hooks/useDismissEntity.ts +++ b/app/soapbox/entity-store/hooks/useDismissEntity.ts @@ -2,15 +2,20 @@ import { useAppDispatch } from 'soapbox/hooks'; import { dismissEntities } from '../actions'; -type EntityPath = [entityType: string, listKey: string] +import { parseEntitiesPath } from './utils'; + +import type { ExpandedEntitiesPath } from './types'; + type DismissFn = (entityId: string) => Promise | T; /** * Removes an entity from a specific list. * To remove an entity globally from all lists, see `useDeleteEntity`. */ -function useDismissEntity(path: EntityPath, dismissFn: DismissFn) { +function useDismissEntity(expandedPath: ExpandedEntitiesPath, dismissFn: DismissFn) { + const path = parseEntitiesPath(expandedPath); const [entityType, listKey] = path; + const dispatch = useAppDispatch(); // TODO: optimistic dismissing diff --git a/app/soapbox/entity-store/hooks/useEntities.ts b/app/soapbox/entity-store/hooks/useEntities.ts index 6945ccd8d..dec6aefd4 100644 --- a/app/soapbox/entity-store/hooks/useEntities.ts +++ b/app/soapbox/entity-store/hooks/useEntities.ts @@ -7,21 +7,11 @@ import { filteredArray } from 'soapbox/schemas/utils'; import { entitiesFetchFail, entitiesFetchRequest, entitiesFetchSuccess } from '../actions'; -import type { Entity, EntityListState } from '../types'; -import type { EntitySchema } from './types'; -import type { RootState } from 'soapbox/store'; +import { parseEntitiesPath } from './utils'; -/** Tells us where to find/store the entity in the cache. */ -type EntityPath = [ - /** Name of the entity type for use in the global cache, eg `'Notification'`. */ - entityType: string, - /** - * Name of a particular index of this entity type. - * Multiple params get combined into one string with a `:` separator. - * You can use empty-string (`''`) if you don't need separate lists. - */ - ...listKeys: string[], -] +import type { Entity, EntityListState } from '../types'; +import type { EntitiesPath, EntitySchema, ExpandedEntitiesPath } from './types'; +import type { RootState } from 'soapbox/store'; /** Additional options for the hook. */ interface UseEntitiesOpts { @@ -39,7 +29,7 @@ interface UseEntitiesOpts { /** A hook for fetching and displaying API entities. */ function useEntities( /** Tells us where to find/store the entity in the cache. */ - path: EntityPath, + expandedPath: ExpandedEntitiesPath, /** API route to GET, eg `'/api/v1/notifications'`. If undefined, nothing will be fetched. */ endpoint: string | undefined, /** Additional options for the hook. */ @@ -49,8 +39,8 @@ function useEntities( const dispatch = useAppDispatch(); const getState = useGetState(); - const [entityType, ...listKeys] = path; - const listKey = listKeys.join(':'); + const path = parseEntitiesPath(expandedPath); + const [entityType, listKey] = path; const entities = useAppSelector(state => selectEntities(state, path)); @@ -128,10 +118,10 @@ function useEntities( } /** Get cache at path from Redux. */ -const selectCache = (state: RootState, path: EntityPath) => state.entities[path[0]]; +const selectCache = (state: RootState, path: EntitiesPath) => state.entities[path[0]]; /** Get list at path from Redux. */ -const selectList = (state: RootState, path: EntityPath) => { +const selectList = (state: RootState, path: EntitiesPath) => { const [, ...listKeys] = path; const listKey = listKeys.join(':'); @@ -139,18 +129,18 @@ const selectList = (state: RootState, path: EntityPath) => { }; /** Select a particular item from a list state. */ -function selectListState(state: RootState, path: EntityPath, key: K) { +function selectListState(state: RootState, path: EntitiesPath, key: K) { const listState = selectList(state, path)?.state; return listState ? listState[key] : undefined; } /** Hook to get a particular item from a list state. */ -function useListState(path: EntityPath, key: K) { +function useListState(path: EntitiesPath, key: K) { return useAppSelector(state => selectListState(state, path, key)); } /** Get list of entities from Redux. */ -function selectEntities(state: RootState, path: EntityPath): readonly TEntity[] { +function selectEntities(state: RootState, path: EntitiesPath): readonly TEntity[] { const cache = selectCache(state, path); const list = selectList(state, path); diff --git a/app/soapbox/entity-store/hooks/useEntity.ts b/app/soapbox/entity-store/hooks/useEntity.ts index 1dad1ff1e..aa7b40b5d 100644 --- a/app/soapbox/entity-store/hooks/useEntity.ts +++ b/app/soapbox/entity-store/hooks/useEntity.ts @@ -6,9 +6,7 @@ import { useApi, useAppDispatch, useAppSelector } from 'soapbox/hooks'; import { importEntities } from '../actions'; import type { Entity } from '../types'; -import type { EntitySchema } from './types'; - -type EntityPath = [entityType: string, entityId: string] +import type { EntitySchema, EntityPath } from './types'; /** Additional options for the hook. */ interface UseEntityOpts { diff --git a/app/soapbox/entity-store/hooks/useEntityActions.ts b/app/soapbox/entity-store/hooks/useEntityActions.ts index 8a259c0e0..96def69ab 100644 --- a/app/soapbox/entity-store/hooks/useEntityActions.ts +++ b/app/soapbox/entity-store/hooks/useEntityActions.ts @@ -2,11 +2,10 @@ import { useApi } from 'soapbox/hooks'; import { useCreateEntity } from './useCreateEntity'; import { useDeleteEntity } from './useDeleteEntity'; +import { parseEntitiesPath } from './utils'; import type { Entity } from '../types'; -import type { EntitySchema } from './types'; - -type EntityPath = [entityType: string, listKey?: string] +import type { EntitySchema, ExpandedEntitiesPath } from './types'; interface UseEntityActionsOpts { schema?: EntitySchema @@ -18,11 +17,12 @@ interface EntityActionEndpoints { } function useEntityActions( - path: EntityPath, + expandedPath: ExpandedEntitiesPath, endpoints: EntityActionEndpoints, opts: UseEntityActionsOpts = {}, ) { const api = useApi(); + const path = parseEntitiesPath(expandedPath); const [entityType] = path; const deleteEntity = useDeleteEntity(entityType, (entityId) => { diff --git a/app/soapbox/entity-store/hooks/utils.ts b/app/soapbox/entity-store/hooks/utils.ts new file mode 100644 index 000000000..69568b25a --- /dev/null +++ b/app/soapbox/entity-store/hooks/utils.ts @@ -0,0 +1,9 @@ +import type { EntitiesPath, ExpandedEntitiesPath } from './types'; + +function parseEntitiesPath(expandedPath: ExpandedEntitiesPath): EntitiesPath { + const [entityType, ...listKeys] = expandedPath; + const listKey = (listKeys || []).join(':'); + return [entityType, listKey]; +} + +export { parseEntitiesPath }; \ No newline at end of file diff --git a/app/soapbox/hooks/useGroups.ts b/app/soapbox/hooks/useGroups.ts index 865896e24..d77b49865 100644 --- a/app/soapbox/hooks/useGroups.ts +++ b/app/soapbox/hooks/useGroups.ts @@ -7,7 +7,7 @@ import { groupRelationshipSchema, GroupRelationship } from 'soapbox/schemas/grou function useGroups() { const { entities, ...result } = useEntities( - [Entities.GROUPS, ''], + [Entities.GROUPS], '/api/v1/groups', { schema: groupSchema }, );