useDeleteEntity: refactor with EntityRequest

This commit is contained in:
Alex Gleason 2023-03-23 15:05:34 -05:00
parent 50f65bc7c9
commit 1c5a6d8b41
No known key found for this signature in database
GPG Key ID: 7211D1F99744FBB7
3 changed files with 28 additions and 22 deletions

View File

@ -13,7 +13,7 @@ interface UseCreateEntityOpts<TEntity extends Entity = Entity> {
schema?: EntitySchema<TEntity> schema?: EntitySchema<TEntity>
} }
interface EntityCallbacks<TEntity extends Entity = Entity, Error = unknown> { interface CreateEntityCallbacks<TEntity extends Entity = Entity, Error = unknown> {
onSuccess?(entity: TEntity): void onSuccess?(entity: TEntity): void
onError?(error: Error): void onError?(error: Error): void
} }
@ -30,7 +30,7 @@ function useCreateEntity<TEntity extends Entity = Entity, Data = any>(
return async function createEntity( return async function createEntity(
data: Data, data: Data,
callbacks: EntityCallbacks = {}, callbacks: CreateEntityCallbacks = {},
): Promise<void> { ): Promise<void> {
try { try {
const result = await api.request({ const result = await api.request({

View File

@ -1,11 +1,14 @@
import { useAppDispatch, useGetState } from 'soapbox/hooks'; import { useApi, useAppDispatch, useGetState } from 'soapbox/hooks';
import { deleteEntities, importEntities } from '../actions'; import { deleteEntities, importEntities } from '../actions';
type DeleteFn<T> = (entityId: string) => Promise<T> | T; import { toAxiosRequest } from './utils';
interface EntityCallbacks { import type { EntityRequest } from './types';
interface DeleteEntityCallbacks {
onSuccess?(): void onSuccess?(): void
onError?(): void
} }
/** /**
@ -13,14 +16,15 @@ interface EntityCallbacks {
* This hook should be used to globally delete an entity from all lists. * This hook should be used to globally delete an entity from all lists.
* To remove an entity from a single list, see `useDismissEntity`. * To remove an entity from a single list, see `useDismissEntity`.
*/ */
function useDeleteEntity<T = unknown>( function useDeleteEntity(
entityType: string, entityType: string,
deleteFn: DeleteFn<T>, request: EntityRequest,
) { ) {
const api = useApi();
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const getState = useGetState(); const getState = useGetState();
return async function deleteEntity(entityId: string, callbacks: EntityCallbacks = {}): Promise<T> { return async function deleteEntity(entityId: string, callbacks: DeleteEntityCallbacks = {}): Promise<void> {
// Get the entity before deleting, so we can reverse the action if the API request fails. // Get the entity before deleting, so we can reverse the action if the API request fails.
const entity = getState().entities[entityType]?.store[entityId]; const entity = getState().entities[entityType]?.store[entityId];
@ -28,21 +32,27 @@ function useDeleteEntity<T = unknown>(
dispatch(deleteEntities([entityId], entityType, { preserveLists: true })); dispatch(deleteEntities([entityId], entityType, { preserveLists: true }));
try { try {
const result = await deleteFn(entityId); // HACK: replace occurrences of `:id` in the URL. Maybe there's a better way?
const axiosReq = toAxiosRequest(request);
axiosReq.url?.replaceAll(':id', entityId);
await api.request(axiosReq);
// Success - finish deleting entity from the state. // Success - finish deleting entity from the state.
dispatch(deleteEntities([entityId], entityType)); dispatch(deleteEntities([entityId], entityType));
if (callbacks.onSuccess) { if (callbacks.onSuccess) {
callbacks.onSuccess(); callbacks.onSuccess();
} }
return result;
} catch (e) { } catch (e) {
if (entity) { if (entity) {
// If the API failed, reimport the entity. // If the API failed, reimport the entity.
dispatch(importEntities([entity], entityType)); dispatch(importEntities([entity], entityType));
} }
throw e;
if (callbacks.onError) {
callbacks.onError();
}
} }
}; };
} }

View File

@ -1,7 +1,5 @@
import { useState } from 'react'; import { useState } from 'react';
import { useApi } from 'soapbox/hooks';
import { useCreateEntity } from './useCreateEntity'; import { useCreateEntity } from './useCreateEntity';
import { useDeleteEntity } from './useDeleteEntity'; import { useDeleteEntity } from './useDeleteEntity';
import { parseEntitiesPath } from './utils'; import { parseEntitiesPath } from './utils';
@ -23,17 +21,10 @@ function useEntityActions<TEntity extends Entity = Entity, Data = any>(
endpoints: EntityActionEndpoints, endpoints: EntityActionEndpoints,
opts: UseEntityActionsOpts<TEntity> = {}, opts: UseEntityActionsOpts<TEntity> = {},
) { ) {
const api = useApi();
const { entityType, path } = parseEntitiesPath(expandedPath); const { entityType, path } = parseEntitiesPath(expandedPath);
const [isLoading, setIsLoading] = useState<boolean>(false); const [isLoading, setIsLoading] = useState<boolean>(false);
const deleteEntity = useDeleteEntity(entityType, (entityId) => { const _delete = useDeleteEntity(entityType, { method: 'delete', url: endpoints.delete });
if (!endpoints.delete) return Promise.reject(endpoints);
return api.delete(endpoints.delete.replace(':id', entityId))
.finally(() => setIsLoading(false));
});
const create = useCreateEntity<TEntity, Data>(path, { method: 'post', url: endpoints.post }, opts); const create = useCreateEntity<TEntity, Data>(path, { method: 'post', url: endpoints.post }, opts);
const createEntity: typeof create = async (...args) => { const createEntity: typeof create = async (...args) => {
@ -41,6 +32,11 @@ function useEntityActions<TEntity extends Entity = Entity, Data = any>(
setIsLoading(false); setIsLoading(false);
}; };
const deleteEntity: typeof _delete = async (...args) => {
await _delete(...args);
setIsLoading(false);
};
return { return {
createEntity, createEntity,
deleteEntity, deleteEntity,