Skip to content

Commit a9f49ce

Browse files
committed
refactor: 페이지 로직을 커스텀 훅으로 분리
- TanStack Query를 사용한 데이터 페칭 및 뮤테이션 로직을 각 feature의 model 디렉토리로 분리하여 재사용성을 높임. - usePosts, useComments, useAddPost 등 관련 훅 생성.
1 parent e166036 commit a9f49ce

File tree

12 files changed

+248
-0
lines changed

12 files changed

+248
-0
lines changed
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import { useMutation, useQueryClient } from "@tanstack/react-query"
2+
import { addCommentApi } from "../../../entities/comments/api"
3+
4+
export const useAddComment = (onSuccessCallback?: () => void) => {
5+
const queryClient = useQueryClient()
6+
7+
return useMutation({
8+
mutationFn: addCommentApi,
9+
onSuccess: (data) => {
10+
queryClient.invalidateQueries({ queryKey: ["comments", data.postId] })
11+
onSuccessCallback?.()
12+
},
13+
onError: (error) => {
14+
console.error("댓글 추가 오류:", error)
15+
},
16+
})
17+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import { useMutation, useQueryClient } from "@tanstack/react-query"
2+
import { addPostApi } from "../../../entities/posts/api"
3+
4+
export const useAddPost = (onSuccessCallback?: () => void) => {
5+
const queryClient = useQueryClient()
6+
7+
return useMutation({
8+
mutationFn: addPostApi,
9+
onSuccess: () => {
10+
queryClient.invalidateQueries({ queryKey: ["posts"] })
11+
onSuccessCallback?.()
12+
},
13+
onError: (error) => {
14+
console.error("게시물 추가 오류:", error)
15+
},
16+
})
17+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import { useQuery } from "@tanstack/react-query"
2+
import { fetchCommentsApi } from "../../../entities/comments/api"
3+
4+
export const useComments = (postId: number | null | undefined) => {
5+
return useQuery({
6+
queryKey: ["comments", postId],
7+
queryFn: () => fetchCommentsApi(postId!),
8+
enabled: !!postId,
9+
})
10+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import { useMutation, useQueryClient } from "@tanstack/react-query"
2+
import { deleteCommentApi } from "../../../entities/comments/api"
3+
4+
export const useDeleteComment = () => {
5+
const queryClient = useQueryClient()
6+
7+
return useMutation({
8+
mutationFn: (params: { id: number; postId: number }) => deleteCommentApi(params.id),
9+
onSuccess: (_data, variables) => {
10+
queryClient.invalidateQueries({ queryKey: ["comments", variables.postId] })
11+
},
12+
onError: (error) => {
13+
console.error("댓글 삭제 오류:", error)
14+
},
15+
})
16+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import { useMutation, useQueryClient } from "@tanstack/react-query"
2+
import { likeCommentApi } from "../../../entities/comments/api"
3+
4+
export const useLikeComment = () => {
5+
const queryClient = useQueryClient()
6+
7+
return useMutation({
8+
mutationFn: (params: { id: number; postId: number }) => likeCommentApi(params.id, 1),
9+
onSuccess: (data) => {
10+
queryClient.invalidateQueries({ queryKey: ["comments", data.postId] })
11+
},
12+
onError: (error) => {
13+
console.error("댓글 좋아요 오류:", error)
14+
},
15+
})
16+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import { useMutation, useQueryClient } from "@tanstack/react-query"
2+
import { updateCommentApi } from "../../../entities/comments/api"
3+
4+
export const useUpdateComment = (onSuccessCallback?: () => void) => {
5+
const queryClient = useQueryClient()
6+
7+
return useMutation({
8+
mutationFn: (updatedComment: { id: number; body: string }) =>
9+
updateCommentApi(updatedComment),
10+
onSuccess: (data) => {
11+
queryClient.invalidateQueries({ queryKey: ["comments", data.postId] })
12+
onSuccessCallback?.()
13+
},
14+
onError: (error) => {
15+
console.error("댓글 업데이트 오류:", error)
16+
},
17+
})
18+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import { useMutation, useQueryClient } from "@tanstack/react-query"
2+
import { updatePostApi, PostDTO } from "../../../entities/posts/api"
3+
4+
export const useEditPost = (onSuccessCallback?: () => void) => {
5+
const queryClient = useQueryClient()
6+
7+
return useMutation({
8+
mutationFn: (params: { selectedPost: PostDTO }) => updatePostApi(params),
9+
onSuccess: () => {
10+
queryClient.invalidateQueries({ queryKey: ["posts"] })
11+
onSuccessCallback?.()
12+
},
13+
onError: (error) => {
14+
console.error("게시물 업데이트 오류:", error)
15+
},
16+
})
17+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import { useMutation, useQueryClient } from "@tanstack/react-query"
2+
import { deletePostApi } from "../../../entities/posts/api"
3+
4+
export const useDeletePost = () => {
5+
const queryClient = useQueryClient()
6+
7+
return useMutation({
8+
mutationFn: deletePostApi,
9+
onSuccess: () => {
10+
queryClient.invalidateQueries({ queryKey: ["posts"] })
11+
},
12+
onError: (error) => {
13+
console.error("게시물 삭제 오류:", error)
14+
},
15+
})
16+
}
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import { useQuery } from "@tanstack/react-query"
2+
import {
3+
fetchPostsApi,
4+
PostDTO,
5+
searchPostsApi,
6+
} from "../../../entities/posts/api"
7+
import { fetchUsersApi } from "../../../entities/users/api"
8+
import { fetchPostsByTagApi } from "../../../entities/posts/api/fetchPostsByTag"
9+
10+
interface UsePostsParams {
11+
skip: number
12+
limit: number
13+
searchQuery: string
14+
sortBy: string
15+
sortOrder: string
16+
selectedTag: string
17+
}
18+
19+
const getPosts = async ({ skip, limit, searchQuery, selectedTag }: UsePostsParams) => {
20+
let postsData
21+
if (searchQuery) {
22+
postsData = await searchPostsApi({ query: searchQuery })
23+
} else if (selectedTag && selectedTag !== "all") {
24+
postsData = await fetchPostsByTagApi(selectedTag)
25+
} else {
26+
postsData = await fetchPostsApi({ limit, skip })
27+
}
28+
29+
const usersData = (await fetchUsersApi()).users
30+
31+
const postsWithUsers = postsData.posts.map((post: PostDTO) => ({
32+
...post,
33+
author: usersData.find((user) => user.id === post.userId),
34+
}))
35+
return { posts: postsWithUsers, total: postsData.total }
36+
}
37+
38+
export const usePosts = ({
39+
skip,
40+
limit,
41+
searchQuery,
42+
sortBy,
43+
sortOrder,
44+
selectedTag,
45+
}: UsePostsParams) => {
46+
return useQuery({
47+
queryKey: ["posts", { skip, limit, searchQuery, sortBy, sortOrder, selectedTag }],
48+
queryFn: () => getPosts({ skip, limit, searchQuery, sortBy, sortOrder, selectedTag }),
49+
placeholderData: (previousData) => previousData,
50+
})
51+
}
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import { useState, useEffect } from "react"
2+
import { useLocation, useNavigate } from "react-router-dom"
3+
4+
export const usePostsQueryState = () => {
5+
const navigate = useNavigate()
6+
const location = useLocation()
7+
const queryParams = new URLSearchParams(location.search)
8+
9+
const [skip, setSkip] = useState(parseInt(queryParams.get("skip") || "0"))
10+
const [limit, setLimit] = useState(parseInt(queryParams.get("limit") || "10"))
11+
const [sortBy, setSortBy] = useState(queryParams.get("sortBy") || "")
12+
const [sortOrder, setSortOrder] = useState(queryParams.get("sortOrder") || "asc")
13+
const [searchQuery, setSearchQuery] = useState(queryParams.get("search") || "")
14+
const [selectedTag, setSelectedTag] = useState(queryParams.get("tag") || "")
15+
16+
useEffect(() => {
17+
const params = new URLSearchParams()
18+
if (skip) params.set("skip", skip.toString())
19+
if (limit) params.set("limit", limit.toString())
20+
if (searchQuery) params.set("search", searchQuery)
21+
if (sortBy) params.set("sortBy", sortBy)
22+
if (sortOrder) params.set("sortOrder", sortOrder)
23+
if (selectedTag) params.set("tag", selectedTag)
24+
navigate(`?${params.toString()}`)
25+
}, [skip, limit, searchQuery, sortBy, sortOrder, selectedTag, navigate])
26+
27+
useEffect(() => {
28+
const params = new URLSearchParams(location.search)
29+
setSkip(parseInt(params.get("skip") || "0"))
30+
setLimit(parseInt(params.get("limit") || "10"))
31+
setSearchQuery(params.get("search") || "")
32+
setSortBy(params.get("sortBy") || "")
33+
setSortOrder(params.get("sortOrder") || "asc")
34+
setSelectedTag(params.get("tag") || "")
35+
}, [location.search])
36+
37+
return {
38+
skip,
39+
setSkip,
40+
limit,
41+
setLimit,
42+
sortBy,
43+
setSortBy,
44+
sortOrder,
45+
setSortOrder,
46+
searchQuery,
47+
setSearchQuery,
48+
selectedTag,
49+
setSelectedTag,
50+
}
51+
}

0 commit comments

Comments
 (0)