EntityStore: optimistic deletion
This commit is contained in:
parent
709edaefad
commit
1ab9b1d75c
3 changed files with 30 additions and 7 deletions
|
@ -16,11 +16,16 @@ function importEntities(entities: Entity[], entityType: string, listKey?: string
|
|||
};
|
||||
}
|
||||
|
||||
function deleteEntities(ids: Iterable<string>, entityType: string) {
|
||||
interface DeleteEntitiesOpts {
|
||||
preserveLists?: boolean
|
||||
}
|
||||
|
||||
function deleteEntities(ids: Iterable<string>, entityType: string, opts: DeleteEntitiesOpts = {}) {
|
||||
return {
|
||||
type: ENTITIES_DELETE,
|
||||
ids,
|
||||
entityType,
|
||||
opts,
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -72,3 +77,5 @@ export {
|
|||
entitiesFetchFail,
|
||||
EntityAction,
|
||||
};
|
||||
|
||||
export type { DeleteEntitiesOpts };
|
|
@ -1,6 +1,6 @@
|
|||
import { z } from 'zod';
|
||||
|
||||
import { useApi, useAppDispatch } from 'soapbox/hooks';
|
||||
import { useApi, useAppDispatch, useGetState } from 'soapbox/hooks';
|
||||
|
||||
import { deleteEntities, importEntities } from '../actions';
|
||||
|
||||
|
@ -35,6 +35,7 @@ function useEntityActions<TEntity extends Entity = Entity, P = any>(
|
|||
) {
|
||||
const api = useApi();
|
||||
const dispatch = useAppDispatch();
|
||||
const getState = useGetState();
|
||||
const [entityType, listKey] = path;
|
||||
|
||||
function createEntity(params: P): Promise<CreateEntityResult<TEntity>> {
|
||||
|
@ -56,13 +57,24 @@ function useEntityActions<TEntity extends Entity = Entity, P = any>(
|
|||
|
||||
function deleteEntity(entityId: string): Promise<DeleteEntityResult> {
|
||||
if (!endpoints.delete) return Promise.reject(endpoints);
|
||||
return api.delete(endpoints.delete.replaceAll(':id', entityId)).then((response) => {
|
||||
// Get the entity before deleting, so we can reverse the action if the API request fails.
|
||||
const entity = getState().entities[entityType]?.store[entityId];
|
||||
// Optimistically delete the entity from the _store_ but keep the lists in tact.
|
||||
dispatch(deleteEntities([entityId], entityType, { preserveLists: true }));
|
||||
|
||||
return api.delete(endpoints.delete.replaceAll(':id', entityId)).then((response) => {
|
||||
// Success - finish deleting entity from the state.
|
||||
dispatch(deleteEntities([entityId], entityType));
|
||||
|
||||
return {
|
||||
response,
|
||||
};
|
||||
}).catch((e) => {
|
||||
if (entity) {
|
||||
// If the API failed, reimport the entity.
|
||||
dispatch(importEntities([entity], entityType));
|
||||
}
|
||||
throw e;
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -10,6 +10,7 @@ import {
|
|||
} from './actions';
|
||||
import { createCache, createList, updateStore, updateList } from './utils';
|
||||
|
||||
import type { DeleteEntitiesOpts } from './actions';
|
||||
import type { Entity, EntityCache, EntityListState } from './types';
|
||||
|
||||
enableMapSet();
|
||||
|
@ -48,6 +49,7 @@ const deleteEntities = (
|
|||
state: State,
|
||||
entityType: string,
|
||||
ids: Iterable<string>,
|
||||
opts: DeleteEntitiesOpts,
|
||||
) => {
|
||||
return produce(state, draft => {
|
||||
const cache = draft[entityType] ?? createCache();
|
||||
|
@ -55,8 +57,10 @@ const deleteEntities = (
|
|||
for (const id of ids) {
|
||||
delete cache.store[id];
|
||||
|
||||
for (const list of Object.values(cache.lists)) {
|
||||
list?.ids.delete(id);
|
||||
if (!opts?.preserveLists) {
|
||||
for (const list of Object.values(cache.lists)) {
|
||||
list?.ids.delete(id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -91,7 +95,7 @@ function reducer(state: Readonly<State> = {}, action: EntityAction): State {
|
|||
case ENTITIES_IMPORT:
|
||||
return importEntities(state, action.entityType, action.entities, action.listKey);
|
||||
case ENTITIES_DELETE:
|
||||
return deleteEntities(state, action.entityType, action.ids);
|
||||
return deleteEntities(state, action.entityType, action.ids, action.opts);
|
||||
case ENTITIES_FETCH_SUCCESS:
|
||||
return importEntities(state, action.entityType, action.entities, action.listKey, action.newState);
|
||||
case ENTITIES_FETCH_REQUEST:
|
||||
|
|
Loading…
Reference in a new issue