Skip to content

Commit

Permalink
Add: Extract a useEntityDelete hook from EntityComponent
Browse files Browse the repository at this point in the history
EntityComponent is a render prop component and can be replaced by
several hooks.
  • Loading branch information
bjoernricks committed Mar 7, 2025
1 parent 0e56ba8 commit 8d02c5a
Show file tree
Hide file tree
Showing 3 changed files with 113 additions and 10 deletions.
16 changes: 6 additions & 10 deletions src/web/entity/EntityComponent.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,10 @@
*/

import {isDefined} from 'gmp/utils/identity';
import {useDispatch} from 'react-redux';
import useEntityDelete from 'web/entity/hooks/useEntityDelete';
import useEntityDownload from 'web/entity/hooks/useEntityDownload';
import {actionFunction} from 'web/entity/hooks/utils';
import useGmp from 'web/hooks/useGmp';
import {createDeleteEntity} from 'web/store/entities/utils/actions';
import PropTypes from 'web/utils/PropTypes';

const EntityComponent = ({
Expand All @@ -27,10 +26,7 @@ const EntityComponent = ({
onCloneError,
}) => {
const gmp = useGmp();
const dispatch = useDispatch();
const cmd = gmp[name];
const deleteEntity = entity =>
dispatch(createDeleteEntity({entityType: name})(gmp)(entity.id));

const handleInteraction = () => {
if (isDefined(onInteraction)) {
Expand All @@ -48,11 +44,11 @@ const EntityComponent = ({
return actionFunction(cmd.create(data), onCreated, onCreateError);
};

const handleEntityDelete = async entity => {
handleInteraction();

return actionFunction(deleteEntity(entity), onDeleted, onDeleteError);
};
const handleEntityDelete = useEntityDelete(name, {
onDeleteError,
onDeleted,
onInteraction,
});

const handleEntityClone = async entity => {
handleInteraction();
Expand Down
62 changes: 62 additions & 0 deletions src/web/entity/hooks/__tests__/useEntityDelete.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
/* SPDX-FileCopyrightText: 2025 Greenbone AG
*
* SPDX-License-Identifier: AGPL-3.0-or-later
*/

import {describe, test, expect, testing} from '@gsa/testing';
import useEntityDelete from 'web/entity/hooks/useEntityDelete';
import {rendererWith, wait} from 'web/utils/Testing';

describe('useEntityDelete', () => {
test('should allow to delete an entity', async () => {
const entity = {id: '123'};
const deleteEntity = testing.fn().mockResolvedValue(entity);
const gmp = {
foo: {delete: deleteEntity},
};
const onDeleted = testing.fn();
const onDeleteError = testing.fn();
const onInteraction = testing.fn();
const {renderHook} = rendererWith({gmp, store: true});
const {result} = renderHook(() =>
useEntityDelete('foo', {
onDeleted,
onDeleteError,
onInteraction,
}),
);
expect(result.current).toBeDefined;
result.current(entity);
await wait();
expect(deleteEntity).toHaveBeenCalledWith(entity);
expect(onDeleted).toHaveBeenCalledOnce();
expect(onDeleteError).not.toHaveBeenCalled();
expect(onInteraction).toHaveBeenCalledOnce();
});

test('should call onDeleteError when deleting an entity fails', async () => {
const deleteEntity = testing.fn().mockRejectedValue(new Error('error'));
const entity = {id: '123'};
const gmp = {
foo: {delete: deleteEntity},
};
const onDeleted = testing.fn();
const onDeleteError = testing.fn();
const onInteraction = testing.fn();
const {renderHook} = rendererWith({gmp, store: true});
const {result} = renderHook(() =>
useEntityDelete('foo', {
onDeleted,
onDeleteError,
onInteraction,
}),
);
expect(result.current).toBeDefined;
result.current(entity);
await wait();
expect(deleteEntity).toHaveBeenCalledWith(entity);
expect(onDeleted).not.toHaveBeenCalled();
expect(onDeleteError).toHaveBeenCalledOnce();
expect(onInteraction).toHaveBeenCalledOnce();
});
});
45 changes: 45 additions & 0 deletions src/web/entity/hooks/useEntityDelete.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
/* SPDX-FileCopyrightText: 2025 Greenbone AG
*
* SPDX-License-Identifier: AGPL-3.0-or-later
*/

import {isDefined} from 'gmp/utils/identity';
import {useDispatch} from 'react-redux';
import {actionFunction} from 'web/entity/hooks/utils';
import useGmp from 'web/hooks/useGmp';
import {createDeleteEntity} from 'web/store/entities/utils/actions';

/**
* Custom hook to handle the deletion of an entity.
*
* @param {string} name - The name of the entity type to be deleted.
* @param {Object} [callbacks] - Optional callbacks for handling delete events.
* @param {Function} [callbacks.onDeleteError] - Callback function to be called if there is an error during deletion.
* @param {Function} [callbacks.onDeleted] - Callback function to be called after the entity is successfully deleted.
* @param {Function} [callbacks.onInteraction] - Callback function to be called during interaction.
* @returns {Function} - A function to handle the deletion of an entity.
*/
const useEntityDelete = (
name,
{onDeleteError, onDeleted, onInteraction} = {},
) => {
const gmp = useGmp();
const dispatch = useDispatch();
const deleteEntity = entity =>
dispatch(createDeleteEntity({entityType: name})(gmp)(entity.id));

const handleInteraction = () => {
if (isDefined(onInteraction)) {
onInteraction();
}
};

const handleEntityDelete = async entity => {
handleInteraction();

return actionFunction(deleteEntity(entity), onDeleted, onDeleteError);
};
return handleEntityDelete;
};

export default useEntityDelete;

0 comments on commit 8d02c5a

Please sign in to comment.