import { useState, useEffect, useCallback, useRef } from 'react'; import { api, type SongsResponse } from '../services/api'; import type { Song } from '../types/interfaces'; interface UsePaginatedSongsOptions { pageSize?: number; initialSearch?: string; playlistName?: string; } export const usePaginatedSongs = (options: UsePaginatedSongsOptions = {}) => { const { pageSize = 50, initialSearch = '', playlistName } = options; const [songs, setSongs] = useState([]); const [loading, setLoading] = useState(false); const [error, setError] = useState(null); const [hasMore, setHasMore] = useState(true); const [currentPage, setCurrentPage] = useState(1); const [searchQuery, setSearchQuery] = useState(initialSearch); const [totalSongs, setTotalSongs] = useState(0); const [isInitialLoad, setIsInitialLoad] = useState(true); const loadingRef = useRef(false); const currentPlaylistRef = useRef(playlistName); const abortControllerRef = useRef(null); // Cleanup function to prevent memory leaks const cleanup = useCallback(() => { if (abortControllerRef.current) { abortControllerRef.current.abort(); abortControllerRef.current = null; } loadingRef.current = false; }, []); // Load songs for a specific page const loadPage = useCallback(async (page: number, search?: string, targetPlaylist?: string) => { if (loadingRef.current) return; const searchToUse = search ?? searchQuery; const playlistToUse = targetPlaylist ?? playlistName; // Cleanup previous request cleanup(); // Create new abort controller for this request abortControllerRef.current = new AbortController(); loadingRef.current = true; setLoading(true); setError(null); try { let response: SongsResponse; if (playlistToUse && playlistToUse !== 'All Songs') { // Load songs for specific playlist response = await api.getPlaylistSongsPaginated(playlistToUse, page, pageSize, searchToUse); } else { // Load all songs response = await api.getSongsPaginated(page, pageSize, searchToUse); } // Check if request was aborted if (abortControllerRef.current?.signal.aborted) { return; } if (page === 1) { // First page - replace all songs setSongs(response.songs); } else { // Subsequent pages - append songs setSongs(prev => [...prev, ...response.songs]); } setHasMore(response.pagination.hasNextPage); setTotalSongs(response.pagination.totalSongs); setCurrentPage(page); } catch (err) { // Don't set error if request was aborted if (!abortControllerRef.current?.signal.aborted) { setError(err instanceof Error ? err.message : 'Failed to load songs'); } } finally { if (!abortControllerRef.current?.signal.aborted) { setLoading(false); loadingRef.current = false; setIsInitialLoad(false); } } }, [pageSize, cleanup]); // Remove searchQuery and playlistName from dependencies // Load next page (for infinite scroll) const loadNextPage = useCallback(() => { if (!loading && hasMore && !loadingRef.current) { loadPage(currentPage + 1); } }, [loading, hasMore, currentPage, loadPage]); // Search songs with debouncing const searchSongs = useCallback((query: string) => { setSearchQuery(query); // Clear songs for new search to replace them setSongs([]); setHasMore(true); setCurrentPage(1); setError(null); loadPage(1, query); }, [loadPage]); // Reset to initial state const reset = useCallback(() => { cleanup(); setSongs([]); setLoading(false); setError(null); setHasMore(true); setCurrentPage(1); setSearchQuery(initialSearch); setIsInitialLoad(true); }, [initialSearch, cleanup]); // Initial load useEffect(() => { loadPage(1); // Cleanup on unmount return () => { cleanup(); }; }, []); // Handle playlist changes useEffect(() => { if (currentPlaylistRef.current !== playlistName) { currentPlaylistRef.current = playlistName; if (!isInitialLoad) { // Clear songs for new playlist to replace them setSongs([]); setHasMore(true); setCurrentPage(1); setSearchQuery(initialSearch); setError(null); // Use setTimeout to avoid the dependency issue setTimeout(() => { loadPage(1, initialSearch, playlistName); }, 0); } } }, [playlistName, isInitialLoad, initialSearch, loadPage]); return { songs, loading, error, hasMore, totalSongs, currentPage, searchQuery, isInitialLoad, loadNextPage, searchSongs, reset, refresh: () => loadPage(1) }; };