EntityStore: consolidate types, fix type of "path"

This commit is contained in:
Alex Gleason 2023-03-22 16:06:10 -05:00
parent d2fd9e0387
commit 8f67d2c76f
No known key found for this signature in database
GPG key ID: 7211D1F99744FBB7
8 changed files with 68 additions and 37 deletions

View file

@ -3,4 +3,30 @@ import type z from 'zod';
type EntitySchema<TEntity extends Entity = Entity> = z.ZodType<TEntity, z.ZodTypeDef, any>;
export type { EntitySchema };
/**
* 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,
};

View file

@ -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, Result> = (params: Params) => Promise<Result> | Result;
interface UseCreateEntityOpts<TEntity extends Entity = Entity> {
@ -30,11 +31,13 @@ interface EntityCallbacks<TEntity extends Entity = Entity, Error = unknown> {
}
function useCreateEntity<TEntity extends Entity = Entity, Params = any, Result = unknown>(
path: EntityPath,
expandedPath: ExpandedEntitiesPath,
createFn: CreateFn<Params, Result>,
opts: UseCreateEntityOpts<TEntity> = {},
) {
const path = parseEntitiesPath(expandedPath);
const [entityType, listKey] = path;
const dispatch = useAppDispatch();
return async function createEntity(

View file

@ -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<T> = (entityId: string) => Promise<T> | T;
/**
* Removes an entity from a specific list.
* To remove an entity globally from all lists, see `useDeleteEntity`.
*/
function useDismissEntity<T = unknown>(path: EntityPath, dismissFn: DismissFn<T>) {
function useDismissEntity<T = unknown>(expandedPath: ExpandedEntitiesPath, dismissFn: DismissFn<T>) {
const path = parseEntitiesPath(expandedPath);
const [entityType, listKey] = path;
const dispatch = useAppDispatch();
// TODO: optimistic dismissing

View file

@ -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<TEntity extends Entity> {
@ -39,7 +29,7 @@ interface UseEntitiesOpts<TEntity extends Entity> {
/** 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,
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<TEntity extends Entity>(
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<TEntity>(state, path));
@ -128,10 +118,10 @@ function useEntities<TEntity extends Entity>(
}
/** 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<K extends keyof EntityListState>(state: RootState, path: EntityPath, key: K) {
function selectListState<K extends keyof EntityListState>(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<K extends keyof EntityListState>(path: EntityPath, key: K) {
function useListState<K extends keyof EntityListState>(path: EntitiesPath, key: K) {
return useAppSelector(state => selectListState(state, path, key));
}
/** Get list of entities from Redux. */
function selectEntities<TEntity extends Entity>(state: RootState, path: EntityPath): readonly TEntity[] {
function selectEntities<TEntity extends Entity>(state: RootState, path: EntitiesPath): readonly TEntity[] {
const cache = selectCache(state, path);
const list = selectList(state, path);

View file

@ -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<TEntity extends Entity> {

View file

@ -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<TEntity extends Entity = Entity> {
schema?: EntitySchema<TEntity>
@ -18,11 +17,12 @@ interface EntityActionEndpoints {
}
function useEntityActions<TEntity extends Entity = Entity, Params = any>(
path: EntityPath,
expandedPath: ExpandedEntitiesPath,
endpoints: EntityActionEndpoints,
opts: UseEntityActionsOpts<TEntity> = {},
) {
const api = useApi();
const path = parseEntitiesPath(expandedPath);
const [entityType] = path;
const deleteEntity = useDeleteEntity(entityType, (entityId) => {

View file

@ -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 };

View file

@ -7,7 +7,7 @@ import { groupRelationshipSchema, GroupRelationship } from 'soapbox/schemas/grou
function useGroups() {
const { entities, ...result } = useEntities<Group>(
[Entities.GROUPS, ''],
[Entities.GROUPS],
'/api/v1/groups',
{ schema: groupSchema },
);