Skip to content
Open
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
5 changes: 4 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,11 @@
"coverage": "vitest run --coverage"
},
"dependencies": {
"@tanstack/react-query": "^5.74.7",
"@tanstack/react-query-devtools": "^5.74.7",
"react": "^19.1.0",
"react-dom": "^19.1.0"
"react-dom": "^19.1.0",
"zustand": "^5.0.3"
},
"devDependencies": {
"@eslint/js": "^9.25.1",
Expand Down
66 changes: 65 additions & 1 deletion pnpm-lock.yaml

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

23 changes: 14 additions & 9 deletions src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,23 @@ import { BrowserRouter as Router } from "react-router-dom"
import Header from "./widgets/ui/Header.tsx"
import Footer from "./widgets/ui/Footer.tsx"
import PostsManagerPage from "./pages/PostsManagerPage.tsx"
import { QueryClient, QueryClientProvider } from "@tanstack/react-query"

const queryClient = new QueryClient()

const App = () => {
return (
<Router>
<div className="flex flex-col min-h-screen">
<Header />
<main className="flex-grow container mx-auto px-4 py-8">
<PostsManagerPage />
</main>
<Footer />
</div>
</Router>
<QueryClientProvider client={queryClient}>
<Router>
<div className="flex flex-col min-h-screen">
<Header />
<main className="flex-grow container mx-auto px-4 py-8">
<PostsManagerPage />
</main>
<Footer />
</div>
</Router>
</QueryClientProvider>
)
}

Expand Down
10 changes: 10 additions & 0 deletions src/entities/posts/api/addPost.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { Post } from "@/entities/posts/model/post.type"

export const addPost = async (newPost: Pick<Post, "title" | "body" | "userId">) => {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pick이라는 기능이 있군요.. TypeScript를 잘 몰라서 이런 기능 있었으면 했는데..

const res = await fetch("/api/posts/add", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(newPost),
})
return res.json()
}
6 changes: 6 additions & 0 deletions src/entities/posts/api/deletePost.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export const deletePost = async (postId: number) => {
const res = await fetch(`/api/posts/${postId}`, {
method: "DELETE",
})
return res.json()
}
4 changes: 4 additions & 0 deletions src/entities/posts/api/getPosts.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export const getPosts = async () => {
const res = await fetch(`/api/posts`)
return res.json()
}
4 changes: 4 additions & 0 deletions src/entities/posts/api/getPostsByPagination.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
// export const getPostsByPagination = async (limit: number, page: number) => {
// const res = await fetch(`/api/posts?limit=${limit}&page=${page}`)
// return res.json()
// }
4 changes: 4 additions & 0 deletions src/entities/posts/api/getPostsByTag.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export const getPostsByTag = async (tag: string) => {
const res = await fetch(`/api/posts/tags/${tag}`)
return res.json()
}
4 changes: 4 additions & 0 deletions src/entities/posts/api/getPostsSearchQuery.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export const getPostsSearchQuery = async (searchQuery: string) => {
const res = await fetch(`/api/posts/search?query=${searchQuery}`)
return res.json()
}
4 changes: 4 additions & 0 deletions src/entities/posts/api/getTags.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export const getTags = async () => {
const res = await fetch("/api/posts/tags")
return res.json()
}
4 changes: 4 additions & 0 deletions src/entities/posts/api/getUniqueTag.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export const getUniqueTag = async (tag: string) => {
const res = await fetch(`/api/posts/tags/${tag}`)
return res.json()
}
13 changes: 13 additions & 0 deletions src/entities/posts/api/updatePost.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { Post } from "@/entities/posts/model/post.type"

export const updatePost = async (selectedPost: Post) => {
const res = await fetch(`/api/posts/${selectedPost.id}`, {
method: "PUT",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(selectedPost),
})
if (res.ok) {
return { message: "ok" }
}
throw new Error("Failed to update post")
}
17 changes: 17 additions & 0 deletions src/entities/posts/model/post.type.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
export interface Post {
id: number
title: string
body: string
tags: Tag[]
reactions: Reaction
userId: number
}

export interface Reaction {
like: number
dislike: number
}
export interface Tag {
url: string
slug: string
}
8 changes: 8 additions & 0 deletions src/entities/posts/model/usePostsMutation.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { useMutation } from "@tanstack/react-query"
import { addPost } from "@/entities/posts/api/addPost"

export const useAddPostsMutation = () => {
return useMutation({
mutationFn: addPost,
})
}
17 changes: 17 additions & 0 deletions src/entities/posts/model/usePostsQuery.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { useQuery } from "@tanstack/react-query"
import { getPosts } from "@/entities/posts/api/getPosts"
import { Post } from "@/entities/posts/model/post.type"

interface PostsResponse {
posts: Post[]
total: number
skip: number
limit: number
}

export const usePostsQuery = () => {
return useQuery<PostsResponse>({
queryKey: ["posts"],

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TanStack Query를 사용하면서 쿼리 키 관리가 번거롭게 느껴질 때가 있는데, 이 문제를 좀 더 쉽게 해결할 수 있도록 도와주는 query-key-factory 라이브러리가 있다고 합니다! TanStack Query에 관심이 있으시면 같이 살펴보시면 좋을 것 같아요!

queryFn: getPosts,
})
}
52 changes: 52 additions & 0 deletions src/entities/posts/model/usePostsStore.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import { create } from "zustand"

interface PostsStoreState {
posts: Post[]
total: number
skip: number
limit: number
setPosts: (posts: Post[]) => void
setTotal: (total: number) => void
setSkip: (skip: number) => void
setLimit: (limit: number) => void
}

export const usePostsStore = create<PostsStoreState>((set) => ({
posts: [],
total: 0,
skip: 0,
limit: 10,
setPosts: (posts) => set((prev) => ({ ...prev, posts })),
setTotal: (total) => set((prev) => ({ ...prev, total })),
setSkip: (skip) => set((prev) => ({ ...prev, skip })),
setLimit: (limit) => set((prev) => ({ ...prev, limit })),
}))

/*
{
"posts": [
{
"id": 1,
"title": "His mother had always taught him",
"body": "His mother had always taught him not to ever think of himself as better than others. He'd tried to live by this motto. He never looked down on those who were less fortunate or who had less money than him. But the stupidity of the group of people he was talking to made him change his mind.",
"tags": [
"history",
"american",
"crime"
],
"reactions": {
"likes": 192,
"dislikes": 25
},
"views": 305,
"userId": 121
},
{...},
{...}
// 30 items
],
"total": 251,
"skip": 0,
"limit": 30
}
*/
28 changes: 28 additions & 0 deletions src/entities/posts/model/usePostsWithUsers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { Post } from "@/entities/posts/model/post.type"
import { useAllPostsQuery } from "@/entities/posts/model/useAllPostsQuery"
import { useAllUsersQuery } from "@/entities/users/model/useAllUsersQuery"

export const usePostsWithUsers = (limit: number, skip: number) => {
const postsQuery = useAllPostsQuery(limit, skip)
const usersQuery = useAllUsersQuery()

const isLoading = postsQuery.isLoading || usersQuery.isLoading
const isError = postsQuery.isError || usersQuery.isError

// TODO: 데이터 조합 따로 함수로 분리
let posts: Post[] = []
if (postsQuery.data && usersQuery.data) {
posts = postsQuery.data.posts.map((post) => ({
...post,
author: usersQuery.data.users.find((user) => user.id === post.userId),
}))
}

return {
posts,
total: postsQuery.data?.total ?? 0,
isLoading,
isError,
error: postsQuery.error || usersQuery.error,
}
}
9 changes: 9 additions & 0 deletions src/entities/posts/model/useTagsList.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { useQuery } from "@tanstack/react-query"
import { getTags } from "@/entities/posts/api/getTags"

export const useTagsList = () => {
return useQuery({
queryKey: ["tags"],
queryFn: getTags,
})
}
10 changes: 10 additions & 0 deletions src/entities/posts/model/useUniqueTag.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { useQuery } from "@tanstack/react-query"
import { getUniqueTag } from "@/entities/posts/api/getUniqueTag"

export const useUniqueTag = (tag: string) => {
return useQuery({
queryKey: ["tags", tag],
queryFn: () => getUniqueTag(tag),
enabled: !!tag,
})
}
4 changes: 4 additions & 0 deletions src/entities/users/api/getUniqueUser.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export const getUniqueUser = async (userId: number) => {
const res = await fetch(`/api/users/${userId}`)
return res.json()
}
4 changes: 4 additions & 0 deletions src/entities/users/api/getUsers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export const getUsers = async () => {
const res = await fetch("/api/users?limit=0&select=username,image")
return res.json()
}
11 changes: 11 additions & 0 deletions src/entities/users/model/useUniqueUserQuery.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { useQuery } from "@tanstack/react-query"
import { getUniqueUser } from "@/entities/users/api/getUniqueUser"
import { User } from "@/entities/users/model/user.type"

export const useUniqueUserQuery = (userId: number) => {
return useQuery<User>({
queryKey: ["user", userId],
queryFn: () => getUniqueUser(userId),
enabled: !!userId,
})
}
Loading