diff --git a/frontend/src/features/songs/hooks/useExtraSongDetail.ts b/frontend/src/features/songs/hooks/useExtraSongDetail.ts new file mode 100644 index 00000000..9e5c1eee --- /dev/null +++ b/frontend/src/features/songs/hooks/useExtraSongDetail.ts @@ -0,0 +1,70 @@ +import { useCallback, useRef } from 'react'; +import useExtraFetch from '@/shared/hooks/useExtraFetch'; +import useValidParams from '@/shared/hooks/useValidParams'; +import createObserver from '@/shared/utils/createObserver'; +import { getExtraNextSongDetails, getExtraPrevSongDetails } from '../remotes/songs'; +import type { Genre } from '../types/Song.type'; + +const useExtraSongDetail = () => { + const { genre: genreParams } = useValidParams(); + + const { data: extraPrevSongDetails, fetchData: fetchExtraPrevSongDetails } = useExtraFetch( + getExtraPrevSongDetails, + 'prev' + ); + + const { data: extraNextSongDetails, fetchData: fetchExtraNextSongDetails } = useExtraFetch( + getExtraNextSongDetails, + 'next' + ); + + const prevObserverRef = useRef(null); + const nextObserverRef = useRef(null); + + const getExtraPrevSongDetailsOnObserve: React.RefCallback = useCallback((dom) => { + if (dom === null) { + prevObserverRef.current?.disconnect(); + return; + } + + prevObserverRef.current = createObserver(() => + fetchExtraPrevSongDetails(getFirstSongId(dom), genreParams as Genre) + ); + + prevObserverRef.current.observe(dom); + }, []); + + const getExtraNextSongDetailsOnObserve: React.RefCallback = useCallback((dom) => { + if (dom === null) { + nextObserverRef.current?.disconnect(); + return; + } + + nextObserverRef.current = createObserver(() => + fetchExtraNextSongDetails(getLastSongId(dom), genreParams as Genre) + ); + + nextObserverRef.current.observe(dom); + }, []); + + const getFirstSongId = (dom: HTMLDivElement) => { + const firstSongId = dom.nextElementSibling?.getAttribute('data-song-id') as string; + + return Number(firstSongId); + }; + + const getLastSongId = (dom: HTMLDivElement) => { + const lastSongId = dom.previousElementSibling?.getAttribute('data-song-id') as string; + + return Number(lastSongId); + }; + + return { + extraPrevSongDetails, + extraNextSongDetails, + getExtraPrevSongDetailsOnObserve, + getExtraNextSongDetailsOnObserve, + }; +}; + +export default useExtraSongDetail; diff --git a/frontend/src/features/songs/hooks/useSongDetailEntries.ts b/frontend/src/features/songs/hooks/useSongDetailEntries.ts new file mode 100644 index 00000000..445f8f50 --- /dev/null +++ b/frontend/src/features/songs/hooks/useSongDetailEntries.ts @@ -0,0 +1,22 @@ +import { useLayoutEffect, useRef } from 'react'; +import useFetch from '@/shared/hooks/useFetch'; +import useValidParams from '@/shared/hooks/useValidParams'; +import { getSongDetailEntries } from '../remotes/songs'; +import type { Genre } from '../types/Song.type'; + +const useSongDetailEntries = () => { + const { id: songIdParams, genre: genreParams } = useValidParams(); + const currentSongDetailItemRef = useRef(null); + + const { data: songDetailEntries } = useFetch(() => + getSongDetailEntries(Number(songIdParams), genreParams as Genre) + ); + + useLayoutEffect(() => { + currentSongDetailItemRef.current?.scrollIntoView({ behavior: 'instant', block: 'start' }); + }, [songDetailEntries]); + + return { songDetailEntries, currentSongDetailItemRef }; +}; + +export default useSongDetailEntries; diff --git a/frontend/src/pages/SongDetailListPage.tsx b/frontend/src/pages/SongDetailListPage.tsx index 650041cc..4d14bfdf 100644 --- a/frontend/src/pages/SongDetailListPage.tsx +++ b/frontend/src/pages/SongDetailListPage.tsx @@ -1,97 +1,35 @@ -import { useCallback, useLayoutEffect, useRef } from 'react'; import { styled } from 'styled-components'; import swipeUpDown from '@/assets/icon/swipe-up-down.svg'; import SongDetailItem from '@/features/songs/components/SongDetailItem'; -import { - getExtraNextSongDetails, - getExtraPrevSongDetails, - getSongDetailEntries, -} from '@/features/songs/remotes/songs'; +import useExtraSongDetail from '@/features/songs/hooks/useExtraSongDetail'; +import useSongDetailEntries from '@/features/songs/hooks/useSongDetailEntries'; import useModal from '@/shared/components/Modal/hooks/useModal'; import Modal from '@/shared/components/Modal/Modal'; import Spacing from '@/shared/components/Spacing'; -import useExtraFetch from '@/shared/hooks/useExtraFetch'; -import useFetch from '@/shared/hooks/useFetch'; import useLocalStorage from '@/shared/hooks/useLocalStorage'; -import useValidParams from '@/shared/hooks/useValidParams'; -import createObserver from '@/shared/utils/createObserver'; -import type { Genre } from '@/features/songs/types/Song.type'; const SongDetailListPage = () => { const { isOpen, closeModal } = useModal(true); const [onboarding, setOnboarding] = useLocalStorage('onboarding', true); - const { id: songIdParams, genre: genreParams } = useValidParams(); - const { data: songDetailEntries } = useFetch(() => - getSongDetailEntries(Number(songIdParams), genreParams as Genre) - ); - - const { data: extraPrevSongDetails, fetchData: fetchExtraPrevSongDetails } = useExtraFetch( - getExtraPrevSongDetails, - 'prev' - ); - - const { data: extraNextSongDetails, fetchData: fetchExtraNextSongDetails } = useExtraFetch( - getExtraNextSongDetails, - 'next' - ); - - const prevObserverRef = useRef(null); - const nextObserverRef = useRef(null); - - const getExtraPrevSongDetailsOnObserve: React.RefCallback = useCallback((dom) => { - if (dom === null) { - prevObserverRef.current?.disconnect(); - return; - } - - prevObserverRef.current = createObserver(() => - fetchExtraPrevSongDetails(getFirstSongId(dom), genreParams as Genre) - ); - - prevObserverRef.current.observe(dom); - }, []); - - const getExtraNextSongDetailsOnObserve: React.RefCallback = useCallback((dom) => { - if (dom === null) { - nextObserverRef.current?.disconnect(); - return; - } - - nextObserverRef.current = createObserver(() => - fetchExtraNextSongDetails(getLastSongId(dom), genreParams as Genre) - ); + const { songDetailEntries, currentSongDetailItemRef } = useSongDetailEntries(); - nextObserverRef.current.observe(dom); - }, []); + const { + extraPrevSongDetails, + extraNextSongDetails, + getExtraPrevSongDetailsOnObserve, + getExtraNextSongDetailsOnObserve, + } = useExtraSongDetail(); - const itemRef = useRef(null); + if (!songDetailEntries) return null; - const getFirstSongId = (dom: HTMLDivElement) => { - const firstSongId = dom.nextElementSibling?.getAttribute('data-song-id') as string; - - return Number(firstSongId); - }; - - const getLastSongId = (dom: HTMLDivElement) => { - const lastSongId = dom.previousElementSibling?.getAttribute('data-song-id') as string; - - return Number(lastSongId); - }; + const { prevSongs, currentSong, nextSongs } = songDetailEntries; const closeCoachMark = () => { setOnboarding(false); closeModal(); }; - useLayoutEffect(() => { - itemRef.current?.scrollIntoView({ behavior: 'instant', block: 'start' }); - }, [songDetailEntries]); - - if (!songDetailEntries) return; - - const { prevSongs, currentSong, nextSongs } = songDetailEntries; - return ( <> {onboarding && ( @@ -122,7 +60,7 @@ const SongDetailListPage = () => { ))} - + {nextSongs.map((nextSongDetail) => (