EntityStore: consolidate types, fix type of "path"
This commit is contained in:
parent
d2fd9e0387
commit
8f67d2c76f
8 changed files with 68 additions and 37 deletions
|
@ -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,
|
||||
};
|
|
@ -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(
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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> {
|
||||
|
|
|
@ -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) => {
|
||||
|
|
9
app/soapbox/entity-store/hooks/utils.ts
Normal file
9
app/soapbox/entity-store/hooks/utils.ts
Normal 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 };
|
|
@ -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 },
|
||||
);
|
||||
|
|
Loading…
Reference in a new issue