From 250b00963576978b30739cfe45580c3e953cef07 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Thu, 9 Mar 2023 12:32:50 -0600 Subject: [PATCH] EntityStore: allow passing a parser function to parse the entities --- app/soapbox/entity-store/hooks/useEntities.ts | 29 +++++++++++++++++-- app/soapbox/entity-store/hooks/useEntity.ts | 18 ++++++++++-- 2 files changed, 42 insertions(+), 5 deletions(-) diff --git a/app/soapbox/entity-store/hooks/useEntities.ts b/app/soapbox/entity-store/hooks/useEntities.ts index e525a86a7..c16abf69b 100644 --- a/app/soapbox/entity-store/hooks/useEntities.ts +++ b/app/soapbox/entity-store/hooks/useEntities.ts @@ -5,14 +5,37 @@ import { entitiesFetchFail, entitiesFetchRequest, entitiesFetchSuccess } from '. import type { Entity } from '../types'; -type EntityPath = [entityType: string, listKey: string] +/** 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. You can use empty-string (`''`) if you don't need separate lists. */ + listKey: string, +] -function useEntities(path: EntityPath, endpoint: string) { +/** Additional options for the hook. */ +interface UseEntitiesOpts { + /** A parser function that returns the desired type, or undefined if validation fails. */ + parser?: (entity: unknown) => TEntity | undefined +} + +/** A hook for fetching and displaying API entities. */ +function useEntities( + /** Tells us where to find/store the entity in the cache. */ + path: EntityPath, + /** API route to GET, eg `'/api/v1/notifications'` */ + endpoint: string, + /** Additional options for the hook. */ + opts: UseEntitiesOpts = {}, +) { const api = useApi(); const dispatch = useAppDispatch(); const [entityType, listKey] = path; + const defaultParser = (entity: unknown) => entity as TEntity; + const parseEntity = opts.parser || defaultParser; + const cache = useAppSelector(state => state.entities[entityType]); const list = cache?.lists[listKey]; @@ -20,7 +43,7 @@ function useEntities(path: EntityPath, endpoint: string) const entities: readonly TEntity[] = entityIds ? ( Array.from(entityIds).reduce((result, id) => { - const entity = cache?.store[id] as TEntity | undefined; + const entity = parseEntity(cache?.store[id] as unknown); if (entity) { result.push(entity); } diff --git a/app/soapbox/entity-store/hooks/useEntity.ts b/app/soapbox/entity-store/hooks/useEntity.ts index 2861a2533..e74e0d5e8 100644 --- a/app/soapbox/entity-store/hooks/useEntity.ts +++ b/app/soapbox/entity-store/hooks/useEntity.ts @@ -8,12 +8,26 @@ import type { Entity } from '../types'; type EntityPath = [entityType: string, entityId: string] -function useEntity(path: EntityPath, endpoint: string) { +/** Additional options for the hook. */ +interface UseEntityOpts { + /** A parser function that returns the desired type, or undefined if validation fails. */ + parser?: (entity: unknown) => TEntity | undefined +} + +function useEntity( + path: EntityPath, + endpoint: string, + opts: UseEntityOpts = {}, +) { const api = useApi(); const dispatch = useAppDispatch(); const [entityType, entityId] = path; - const entity = useAppSelector(state => state.entities[entityType]?.store[entityId]) as TEntity | undefined; + + const defaultParser = (entity: unknown) => entity as TEntity; + const parseEntity = opts.parser || defaultParser; + + const entity = useAppSelector(state => parseEntity(state.entities[entityType]?.store[entityId])); const [isFetching, setIsFetching] = useState(false); const isLoading = isFetching && !entity;