EntityStore: allow passing a parser function to parse the entities
This commit is contained in:
parent
9964491da5
commit
250b009635
2 changed files with 42 additions and 5 deletions
|
@ -5,14 +5,37 @@ import { entitiesFetchFail, entitiesFetchRequest, entitiesFetchSuccess } from '.
|
||||||
|
|
||||||
import type { Entity } from '../types';
|
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<TEntity extends Entity>(path: EntityPath, endpoint: string) {
|
/** Additional options for the hook. */
|
||||||
|
interface UseEntitiesOpts<TEntity> {
|
||||||
|
/** 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<TEntity extends Entity>(
|
||||||
|
/** 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<TEntity> = {},
|
||||||
|
) {
|
||||||
const api = useApi();
|
const api = useApi();
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
|
|
||||||
const [entityType, listKey] = path;
|
const [entityType, listKey] = path;
|
||||||
|
|
||||||
|
const defaultParser = (entity: unknown) => entity as TEntity;
|
||||||
|
const parseEntity = opts.parser || defaultParser;
|
||||||
|
|
||||||
const cache = useAppSelector(state => state.entities[entityType]);
|
const cache = useAppSelector(state => state.entities[entityType]);
|
||||||
const list = cache?.lists[listKey];
|
const list = cache?.lists[listKey];
|
||||||
|
|
||||||
|
@ -20,7 +43,7 @@ function useEntities<TEntity extends Entity>(path: EntityPath, endpoint: string)
|
||||||
|
|
||||||
const entities: readonly TEntity[] = entityIds ? (
|
const entities: readonly TEntity[] = entityIds ? (
|
||||||
Array.from(entityIds).reduce<TEntity[]>((result, id) => {
|
Array.from(entityIds).reduce<TEntity[]>((result, id) => {
|
||||||
const entity = cache?.store[id] as TEntity | undefined;
|
const entity = parseEntity(cache?.store[id] as unknown);
|
||||||
if (entity) {
|
if (entity) {
|
||||||
result.push(entity);
|
result.push(entity);
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,12 +8,26 @@ import type { Entity } from '../types';
|
||||||
|
|
||||||
type EntityPath = [entityType: string, entityId: string]
|
type EntityPath = [entityType: string, entityId: string]
|
||||||
|
|
||||||
function useEntity<TEntity extends Entity>(path: EntityPath, endpoint: string) {
|
/** Additional options for the hook. */
|
||||||
|
interface UseEntityOpts<TEntity> {
|
||||||
|
/** A parser function that returns the desired type, or undefined if validation fails. */
|
||||||
|
parser?: (entity: unknown) => TEntity | undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
function useEntity<TEntity extends Entity>(
|
||||||
|
path: EntityPath,
|
||||||
|
endpoint: string,
|
||||||
|
opts: UseEntityOpts<TEntity> = {},
|
||||||
|
) {
|
||||||
const api = useApi();
|
const api = useApi();
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
|
|
||||||
const [entityType, entityId] = path;
|
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 [isFetching, setIsFetching] = useState(false);
|
||||||
const isLoading = isFetching && !entity;
|
const isLoading = isFetching && !entity;
|
||||||
|
|
Loading…
Reference in a new issue