Back

100% Statements 21/21
100% Branches 10/10
100% Functions 9/9
100% Lines 19/19

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88                                3x 3x 3x                                         34x 34x   7x 7x   11x           11x 7x 3x               7x 7x 11x 7x 8x             7x             7x   3x            
import { useMutation, useQueryClient } from '@tanstack/react-query';
import apiFetch from '@wordpress/api-fetch';
import { LIBRARY_ITEM_QUERY_SEGMENT, LIBRARY_QUERY_KEY } from './use-library';
import { removeUploadRowsForMedia } from './use-upload';
 
type Id = number | string;
 
/**
 * Thrown when one or more deletions in a batch fail. Carries the ids that
 * failed so callers can report precise counts and keep the surviving rows
 * selected; the other ids in the batch were deleted successfully.
 */
export class DeleteVideosError extends Error {
	failedIds: Id[];
 
	constructor( failedIds: Id[] ) {
		super( `Failed to delete ${ failedIds.length } video(s)` );
		this.name = 'DeleteVideosError';
		this.failedIds = failedIds;
	}
}
 
/**
 * Return a mutation that permanently deletes one or more videos via DELETE
 * /wp/v2/media/{id}?force=true. Requests run in parallel; if any fail the
 * mutation rejects with a DeleteVideosError listing the failed ids (the rest
 * stay deleted). The library cache is invalidated on settle — success and
 * partial failure both change the listing — and the invalidation promise is
 * returned so the mutation settles only after active queries refetch, letting
 * row-level "Deleting…" states persist until rows actually disappear.
 *
 * Callers should react via the mutateAsync promise rather than mutate-level
 * callbacks: TanStack detaches the observer from an in-flight mutation when
 * the same hook instance starts another one, silently dropping that call's
 * mutate-level callbacks — the promise always settles.
 *
 * @return A TanStack Query mutation object whose mutateAsync accepts a single id or an array of ids.
 */
export function useDeleteVideo() {
	const client = useQueryClient();
	return useMutation< void, Error, Id | Id[] >( {
		mutationFn: async input => {
			const ids = Array.isArray( input ) ? input : [ input ];
			const results = await Promise.allSettled(
				ids.map( id =>
					apiFetch( {
						path: `/wp/v2/media/${ id }?force=true`,
						method: 'DELETE',
					} )
				)
			);
			const failedIds = ids.filter( ( _, index ) => results[ index ].status === 'rejected' );
			if ( failedIds.length > 0 ) {
				throw new DeleteVideosError( failedIds );
			}
		},
		onSettled: ( _data, error, input ) => {
			// Drop the deleted ids' item-detail queries outright. Invalidating
			// them is no help — a refetch 404s and TanStack retains the stale
			// data — so a cached entry would let a back-navigation render a
			// ghost editor for a video that no longer exists.
			const ids = Array.isArray( input ) ? input : [ input ];
			const failedIds = error instanceof DeleteVideosError ? new Set( error.failedIds ) : null;
			const deletedIds = ids.filter( id => ! failedIds?.has( id ) );
			deletedIds.forEach( id =>
				client.removeQueries( {
					queryKey: [ LIBRARY_QUERY_KEY, LIBRARY_ITEM_QUERY_SEGMENT, String( id ) ],
				} )
			);
			// The upload queue outlives the video: a success row nobody
			// acknowledged keeps offering "Add details" for a 404, and gets
			// re-adopted into a permanently "processing" edit session.
			removeUploadRowsForMedia( deletedIds );
			// Predicate rather than a key prefix: plain [ LIBRARY_QUERY_KEY ]
			// would also match useVideo's item queries — on the details page
			// that query is active for the id being deleted, and the refetch
			// would 404 (+ one retry) and stall settling by seconds. Deleting
			// can't change other items' detail data, so item queries are
			// excluded entirely.
			return client.invalidateQueries( {
				predicate: query =>
					query.queryKey[ 0 ] === LIBRARY_QUERY_KEY &&
					query.queryKey[ 1 ] !== LIBRARY_ITEM_QUERY_SEGMENT,
			} );
		},
	} );
}