Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat/#558 스와이프, 댓글 기능에 React-Query 적용 #559

Merged
merged 5 commits into from
Dec 26, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 53 additions & 0 deletions frontend/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
},
"license": "MIT",
"dependencies": {
"@tanstack/react-query": "^5.14.1",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-router-dom": "^6.14.1",
Expand All @@ -33,6 +34,7 @@
"@storybook/react": "^7.0.27",
"@storybook/react-webpack5": "^7.0.27",
"@storybook/testing-library": "^0.0.14-next.2",
"@tanstack/react-query-devtools": "^5.14.1",
"@testing-library/jest-dom": "^5.16.5",
"@testing-library/react": "^14.0.0",
"@testing-library/user-event": "^14.4.3",
Expand Down
34 changes: 21 additions & 13 deletions frontend/src/features/comments/components/CommentForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,25 +7,24 @@ import LoginModal from '@/features/auth/components/LoginModal';
import Avatar from '@/shared/components/Avatar';
import useModal from '@/shared/components/Modal/hooks/useModal';
import useToastContext from '@/shared/components/Toast/hooks/useToastContext';
import { useMutation } from '@/shared/hooks/useMutation';
import { postComment } from '../remotes/comments';
import { usePostCommentMutation } from '../queries';

interface CommentFormProps {
getComments: () => Promise<void>;
songId: number;
partId: number;
}

const CommentForm = ({ getComments, songId, partId }: CommentFormProps) => {
const CommentForm = ({ songId, partId }: CommentFormProps) => {
const [newComment, setNewComment] = useState('');
const { isOpen, closeModal: closeLoginModal, openModal: openLoginModal } = useModal();
const { user } = useAuthContext();

const isLoggedIn = !!user;

const { mutateData: postNewComment } = useMutation(() =>
postComment(songId, partId, newComment.trim())
);
const {
postNewComment,
mutations: { isPending: isPendingPostComment },
} = usePostCommentMutation();

const { showToast } = useToastContext();

Expand All @@ -35,14 +34,18 @@ const CommentForm = ({ getComments, songId, partId }: CommentFormProps) => {
currentTarget: { value },
}) => setNewComment(value);

const submitNewComment: React.FormEventHandler<HTMLFormElement> = async (event) => {
const submitNewComment: React.FormEventHandler<HTMLFormElement> = (event) => {
event.preventDefault();

await postNewComment();

showToast('댓글이 등록되었습니다.');
resetNewComment();
await getComments();
postNewComment(
{ songId, partId, content: newComment.trim() },
{
onSuccess: () => {
showToast('댓글이 등록되었습니다.');
resetNewComment();
},
}
);
};

return (
Expand All @@ -53,6 +56,7 @@ const CommentForm = ({ getComments, songId, partId }: CommentFormProps) => {
<Avatar src={shookshook} alt="슉슉이" />
<Input
type="text"
disabled={isPendingPostComment}
value={newComment}
onChange={changeNewComment}
placeholder="댓글 추가..."
Expand Down Expand Up @@ -123,6 +127,10 @@ const Input = styled.input`
outline: none;
-webkit-box-shadow: none;
box-shadow: none;

&:disabled {
color: ${({ theme: { color } }) => color.disabledBackground};
}
`;

const FlexEnd = styled.div`
Expand Down
14 changes: 3 additions & 11 deletions frontend/src/features/comments/components/CommentList.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,10 @@
import { useEffect } from 'react';
import { styled } from 'styled-components';
import cancelIcon from '@/assets/icon/cancel.svg';
import BottomSheet from '@/shared/components/BottomSheet/BottomSheet';
import useModal from '@/shared/components/Modal/hooks/useModal';
import Spacing from '@/shared/components/Spacing';
import SRHeading from '@/shared/components/SRHeading';
import useFetch from '@/shared/hooks/useFetch';
import { getComments } from '../remotes/comments';
import { useCommentsQuery } from '../queries';
import Comment from './Comment';
import CommentForm from './CommentForm';

Expand All @@ -17,13 +15,7 @@ interface CommentListProps {

const CommentList = ({ songId, partId }: CommentListProps) => {
const { isOpen, openModal, closeModal } = useModal(false);
const { data: comments, fetchData: refetchComments } = useFetch(() =>
getComments(songId, partId)
);

useEffect(() => {
refetchComments();
}, [partId]);
const { comments } = useCommentsQuery(songId, partId);

if (!comments) {
return null;
Expand Down Expand Up @@ -66,7 +58,7 @@ const CommentList = ({ songId, partId }: CommentListProps) => {
))}
</Comments>
<Spacing direction="vertical" size={8} />
<CommentForm getComments={refetchComments} songId={songId} partId={partId} />
<CommentForm songId={songId} partId={partId} />
</BottomSheet>
</>
);
Expand Down
24 changes: 24 additions & 0 deletions frontend/src/features/comments/queries/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
import { getComments, postComment } from '../remotes/comments';

export const useCommentsQuery = (songId: number, partId: number) => {
const { data: comments, ...queries } = useQuery({
queryKey: ['comments', songId, partId],
queryFn: () => getComments(songId, partId),
});

return { comments, queries };
};

export const usePostCommentMutation = () => {
const client = useQueryClient();

const { mutate: postNewComment, ...mutations } = useMutation({
mutationFn: postComment,
onSuccess: (_, { songId, partId }) => {
client.invalidateQueries({ queryKey: ['comments', songId, partId] });
},
});

return { postNewComment, mutations };
};
10 changes: 9 additions & 1 deletion frontend/src/features/comments/remotes/comments.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,15 @@
import { client } from '@/shared/remotes/axios';
import type { Comment } from '../types/comment.type';

export const postComment = async (songId: number, partId: number, content: string) => {
export const postComment = async ({
songId,
partId,
content,
}: {
songId: number;
partId: number;
content: string;
}) => {
await client.post(`/songs/${songId}/parts/${partId}/comments`, { content });
};

Expand Down
50 changes: 21 additions & 29 deletions frontend/src/features/songs/hooks/useExtraSongDetail.ts
Original file line number Diff line number Diff line change
@@ -1,67 +1,59 @@
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 {
useExtraNextSongDetailsInfiniteQuery,
useExtraPrevSongDetailsInfiniteQuery,
} from '../queries';
import type { Genre } from '../types/Song.type';

const useExtraSongDetail = () => {
const { genre: genreParams } = useValidParams();
const { id: songIdParams, genre: genreParams } = useValidParams();

const { data: extraPrevSongDetails, fetchData: fetchExtraPrevSongDetails } = useExtraFetch(
getExtraPrevSongDetails,
'prev'
);
const {
extraPrevSongDetails,
fetchExtraPrevSongDetails,
infiniteQueries: { isLoading: isLoadingPrevSongDetails, hasPreviousPage },
} = useExtraPrevSongDetailsInfiniteQuery(Number(songIdParams), genreParams as Genre);

const { data: extraNextSongDetails, fetchData: fetchExtraNextSongDetails } = useExtraFetch(
getExtraNextSongDetails,
'next'
);
const {
extraNextSongDetails,
fetchExtraNextSongDetails,
infiniteQueries: { isLoading: isLoadingNextSongDetails, hasNextPage },
} = useExtraNextSongDetailsInfiniteQuery(Number(songIdParams), genreParams as Genre);

const prevObserverRef = useRef<IntersectionObserver | null>(null);
const nextObserverRef = useRef<IntersectionObserver | null>(null);

const getExtraPrevSongDetailsOnObserve: React.RefCallback<HTMLDivElement> = useCallback((dom) => {
if (dom !== null) {
prevObserverRef.current = createObserver(() =>
fetchExtraPrevSongDetails(getFirstSongId(dom), genreParams as Genre)
);
prevObserverRef.current = createObserver(() => fetchExtraPrevSongDetails());

prevObserverRef.current.observe(dom);
return;
}

prevObserverRef.current?.disconnect();
}, []);

Check warning on line 37 in frontend/src/features/songs/hooks/useExtraSongDetail.ts

View workflow job for this annotation

GitHub Actions / test

React Hook useCallback has a missing dependency: 'fetchExtraPrevSongDetails'. Either include it or remove the dependency array

const getExtraNextSongDetailsOnObserve: React.RefCallback<HTMLDivElement> = useCallback((dom) => {
if (dom !== null) {
nextObserverRef.current = createObserver(() =>
fetchExtraNextSongDetails(getLastSongId(dom), genreParams as Genre)
);
nextObserverRef.current = createObserver(() => fetchExtraNextSongDetails());

nextObserverRef.current.observe(dom);
return;
}

nextObserverRef.current?.disconnect();
}, []);

Check warning on line 48 in frontend/src/features/songs/hooks/useExtraSongDetail.ts

View workflow job for this annotation

GitHub Actions / test

React Hook useCallback has a missing dependency: 'fetchExtraNextSongDetails'. Either include it or remove the dependency array

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,
isLoadingPrevSongDetails,
isLoadingNextSongDetails,
hasPreviousPage,
hasNextPage,
getExtraPrevSongDetailsOnObserve,
getExtraNextSongDetailsOnObserve,
};
Expand Down
12 changes: 6 additions & 6 deletions frontend/src/features/songs/hooks/useSongDetailEntries.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,21 @@
import { useCallback } from 'react';
import useFetch from '@/shared/hooks/useFetch';
import useValidParams from '@/shared/hooks/useValidParams';
import { getSongDetailEntries } from '../remotes/songs';
import { useSongDetailEntriesQuery } from '../queries';
import type { Genre } from '../types/Song.type';

const useSongDetailEntries = () => {
const { id: songIdParams, genre: genreParams } = useValidParams();

const { data: songDetailEntries } = useFetch(() =>
getSongDetailEntries(Number(songIdParams), genreParams as Genre)
);
const {
songDetailEntries,
queries: { isLoading: isLoadingSongDetailEntries },
} = useSongDetailEntriesQuery(Number(songIdParams), genreParams as Genre);

const scrollIntoCurrentSong: React.RefCallback<HTMLDivElement> = useCallback((dom) => {
if (dom !== null) dom.scrollIntoView({ behavior: 'instant', block: 'start' });
}, []);

return { songDetailEntries, scrollIntoCurrentSong };
return { songDetailEntries, isLoadingSongDetailEntries, scrollIntoCurrentSong };
};

export default useSongDetailEntries;
Loading
Loading