refactor(playlist): G.70 — extract three lifecycle hooks (route + bulk-picker dismiss + DnD reorder) (#637)

Three-cut cluster pulling the last cluster of useEffect bodies + the
DnD-reorder visual feedback out of PlaylistDetail.tsx. 452 → 423 LOC
(−29).

usePlaylistRouteEffects — bundles two route-driven effects:
contextMenu reset (clears contextMenuSongId whenever playerStore's
context menu closes) and openEditMeta-from-route (consumes the
`openEditMeta` location.state flag, opens the meta modal, and
clears the flag with a navigate-replace so back-nav doesn't
re-trigger). Subscribes to playerStore directly.

useBulkPlPickerOutsideClick — the global mousedown listener that
closes the bulk-add-to-playlist picker when clicking outside the
picker wrapper. No-op when closed.

usePlaylistDnDReorder — owns dropTargetIdx state, the
container.addEventListener('psy-drop', …) wiring that calls
runPlaylistReorderDrop, and the handleRowMouseEnter
drag-over-visual helper. Subscribes to DragDropContext directly.

PlaylistDetail drops the direct runPlaylistReorderDrop import and
the contextMenuOpen store subscription — both now live in the hooks
that need them. Pure code move otherwise.
This commit is contained in:
Frank Stellmacher
2026-05-13 14:01:22 +02:00
committed by GitHub
parent 16ee1ea373
commit e260669537
4 changed files with 95 additions and 45 deletions
+27
View File
@@ -0,0 +1,27 @@
import React, { useEffect } from 'react';
import type { Location, NavigateFunction } from 'react-router-dom';
import { usePlayerStore } from '../store/playerStore';
export interface PlaylistRouteEffectsDeps {
setContextMenuSongId: React.Dispatch<React.SetStateAction<string | null>>;
setEditingMeta: React.Dispatch<React.SetStateAction<boolean>>;
location: Location;
navigate: NavigateFunction;
}
export function usePlaylistRouteEffects(deps: PlaylistRouteEffectsDeps): void {
const { setContextMenuSongId, setEditingMeta, location, navigate } = deps;
const contextMenuOpen = usePlayerStore(s => s.contextMenu.isOpen);
useEffect(() => {
if (!contextMenuOpen) setContextMenuSongId(null);
}, [contextMenuOpen, setContextMenuSongId]);
useEffect(() => {
const state = (location.state as { openEditMeta?: boolean } | null) ?? null;
if (state?.openEditMeta) {
setEditingMeta(true);
navigate(location.pathname, { replace: true, state: null });
}
}, [location.state, location.pathname, navigate, setEditingMeta]);
}