Skip to content

Commit

Permalink
feat: added hooks
Browse files Browse the repository at this point in the history
  • Loading branch information
warmachine028 committed Oct 24, 2024
1 parent d83fb4a commit 6c8bda0
Show file tree
Hide file tree
Showing 6 changed files with 176 additions and 170 deletions.
4 changes: 2 additions & 2 deletions client/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<link rel="icon" type="image/png+xml" href="https://query.gg/favicon.png" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vite + React + TS</title>
<title>React Query Demo</title>
</head>
<body>
<div id="root"></div>
Expand Down
22 changes: 1 addition & 21 deletions client/src/api/posts.ts
Original file line number Diff line number Diff line change
@@ -1,25 +1,5 @@
import axios from 'axios'

interface Post {
id: number
title: string
body: string
userId: number
imageUrl: string
tags: string[]
reactions: {
likes: number
dislikes: number
}
views: number
}

interface PostsResponse {
posts: Post[]
total: number
skip: number
limit: number
}
import { PostsResponse, Post } from '@/types'

const API_URL = 'https://dummyjson.com'

Expand Down
131 changes: 131 additions & 0 deletions client/src/hooks/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
import { PostPage } from '@/types'
import { useContext } from 'react'
import { ThemeContext } from '@/contexts'
import { createPost, deletePost, getPosts, updatePost } from '@/api'
import { useInfiniteQuery, useMutation, useQueryClient } from '@tanstack/react-query'

export const useTheme = () => {
const context = useContext(ThemeContext)
Expand All @@ -10,3 +13,131 @@ export const useTheme = () => {

return context
}

export const useCreatePost = () => {
const queryClient = useQueryClient()

return useMutation({
mutationFn: createPost,
onMutate: async (newPost) => {
await queryClient.cancelQueries({ queryKey: ['posts'] })
const previousData = queryClient.getQueryData(['posts'])

queryClient.setQueryData<{ pages: PostPage[]; pageParams: number[] }>(['posts'], (old) => {
if (!old) {
return { pages: [], pageParams: [] }
}
return {
...old,
pages: [
{
posts: [
{
...newPost,
id: Date.now(),
imageUrl: `https://picsum.photos/seed/${Date.now()}/800/600`,
views: 0,
reactions: {
likes: 0,
dislikes: 0
}
}
],
nextCursor: old.pages[0]?.nextCursor
},
...old.pages
]
}
})

return { previousData }
},
onError: (_err, _newPost, context) => {
queryClient.setQueryData(['posts'], context?.previousData)
},
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['posts'] })
}
})
}

export const useUpdatePost = () => {
const queryClient = useQueryClient()

return useMutation({
mutationFn: updatePost,
onMutate: async (updatedPost) => {
await queryClient.cancelQueries({ queryKey: ['posts'] })
const previousData = queryClient.getQueryData(['posts'])

queryClient.setQueryData<{ pages: PostPage[]; pageParams: number[] }>(['posts'], (old) => {
if (!old) return { pages: [], pageParams: [] }
return {
...old,
pages: old.pages.map((page) => ({
...page,
posts: page.posts.map((post) =>
post.id === updatedPost.id ? { ...post, ...updatedPost } : post
)
}))
}
})

return { previousData }
},
onError: (_err, _updatedPost, context) => {
queryClient.setQueryData(['posts'], context?.previousData)
},
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['posts'] })
}
})
}

export const useDeletePost = () => {
const queryClient = useQueryClient()

return useMutation({
mutationFn: deletePost,
onMutate: async (postId) => {
await queryClient.cancelQueries({ queryKey: ['posts'] })
const previousData = queryClient.getQueryData(['posts'])

queryClient.setQueryData<{ pages: PostPage[]; pageParams: number[] }>(['posts'], (old) => {
if (!old) {
return { pages: [], pageParams: [] }
}
return {
...old,
pages: old.pages.map((page) => ({
...page,
posts: page.posts.filter((post) => post.id !== postId)
}))
}
})

return { previousData }
},
onError: (_err, _postId, context) => {
queryClient.setQueryData(['posts'], context?.previousData)
},
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['posts'] })
}
})
}

export const useGetPosts = () => {
return useInfiniteQuery({
queryKey: ['posts'],
queryFn: async ({ pageParam = 0 }) => {
const response = await getPosts(pageParam, 10)
return {
posts: response.posts,
nextCursor: pageParam + 10 < response.total ? pageParam + 10 : undefined
}
},
getNextPageParam: (lastPage) => lastPage.nextCursor,
initialPageParam: 1
})
}
2 changes: 2 additions & 0 deletions client/src/lib/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,5 @@ import { clsx, type ClassValue } from 'clsx'
import { twMerge } from 'tailwind-merge'

export const cn = (...inputs: ClassValue[]) => twMerge(clsx(inputs))

export const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms))
161 changes: 14 additions & 147 deletions client/src/pages/Posts.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,25 +18,8 @@ import {
DialogTitle,
DialogTrigger
} from '@/components/ui/dialog'

interface Post {
id: number
title: string
body: string
userId: number
imageUrl: string
tags: string[]
reactions: {
likes: number
dislikes: number
}
views: number
}

interface PostPage {
posts: Post[]
nextCursor?: number
}
import { useCreatePost, useDeletePost, useGetPosts, useUpdatePost } from '@/hooks'
import { type Post } from '@/types'

const tagColors = [
'bg-red-100 text-red-800',
Expand All @@ -49,134 +32,18 @@ const tagColors = [
]

export default function Posts() {
const queryClient = useQueryClient()
const [newPostTitle, setNewPostTitle] = useState('')
const [newPostBody, setNewPostBody] = useState('')
const [newPostTags, setNewPostTags] = useState('')
const [newPostImage, setNewPostImage] = useState<File | null>(null)
const [_newPostImage, setNewPostImage] = useState<File | null>(null)
const [editingPost, setEditingPost] = useState<Post | null>(null)
const [deletePostId, setDeletePostId] = useState<number | null>(null)
const { ref, inView } = useInView()

const { data, fetchNextPage, hasNextPage, isFetchingNextPage, status } = useInfiniteQuery({
queryKey: ['posts'],
queryFn: async ({ pageParam = 0 }) => {
const response = await getPosts(pageParam, 10)
return {
posts: response.posts,
nextCursor: pageParam + 10 < response.total ? pageParam + 10 : undefined
}
},
getNextPageParam: (lastPage) => lastPage.nextCursor,
initialPageParam: 1
})

const addMutation = useMutation({
mutationFn: createPost,
onMutate: async (newPost) => {
await queryClient.cancelQueries({ queryKey: ['posts'] })
const previousData = queryClient.getQueryData(['posts'])

queryClient.setQueryData<{ pages: PostPage[]; pageParams: number[] }>(['posts'], (old) => {
if (!old) {
return { pages: [], pageParams: [] }
}
return {
...old,
pages: [
{
posts: [
{
...newPost,
id: Date.now(),
imageUrl: newPostImage
? URL.createObjectURL(newPostImage)
: `https://picsum.photos/seed/${Date.now()}/800/600`,
views: 0,
reactions: {
likes: 0,
dislikes: 0
}
}
],
nextCursor: old.pages[0]?.nextCursor
},
...old.pages
]
}
})

return { previousData }
},
onError: (_err, _newPost, context) => {
queryClient.setQueryData(['posts'], context?.previousData)
},
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['posts'] })
setNewPostTitle('')
setNewPostBody('')
setNewPostTags('')
setNewPostImage(null)
}
})

const updateMutation = useMutation({
mutationFn: updatePost,
onMutate: async (updatedPost) => {
await queryClient.cancelQueries({ queryKey: ['posts'] })
const previousData = queryClient.getQueryData(['posts'])

queryClient.setQueryData<{ pages: PostPage[]; pageParams: number[] }>(['posts'], (old) => {
if (!old) return { pages: [], pageParams: [] }
return {
...old,
pages: old.pages.map((page) => ({
...page,
posts: page.posts.map((post) =>
post.id === updatedPost.id ? { ...post, ...updatedPost } : post
)
}))
}
})

return { previousData }
},
onError: (_err, _updatedPost, context) => {
queryClient.setQueryData(['posts'], context?.previousData)
},
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['posts'] })
setEditingPost(null)
}
})

const deleteMutation = useMutation({
mutationFn: deletePost,
onMutate: async (postId) => {
await queryClient.cancelQueries({ queryKey: ['posts'] })
const previousData = queryClient.getQueryData(['posts'])

queryClient.setQueryData<{ pages: PostPage[]; pageParams: number[] }>(['posts'], (old) => {
if (!old) return { pages: [], pageParams: [] }
return {
...old,
pages: old.pages.map((page) => ({
...page,
posts: page.posts.filter((post) => post.id !== postId)
}))
}
})

return { previousData }
},
onError: (_err, _postId, context) => {
queryClient.setQueryData(['posts'], context?.previousData)
},
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['posts'] })
setDeletePostId(null)
}
})
const { data, fetchNextPage, hasNextPage, isFetchingNextPage, status } = useGetPosts()
const addMutation = useCreatePost()
const updateMutation = useUpdatePost()
const deleteMutation = useDeletePost()

if (inView && hasNextPage) {
fetchNextPage()
Expand All @@ -199,15 +66,15 @@ export default function Posts() {
}
})
}
setNewPostTitle('')
setNewPostBody('')
setNewPostTags('')
setNewPostImage(null)
}

const handleUpdatePost = (_post: Post) => {
if (editingPost) {
updateMutation.mutate({
...editingPost,
tags: editingPost.tags
})
}
const handleUpdatePost = (post: Post) => {
updateMutation.mutate({ ...post, tags: post.tags })
setEditingPost(null)
}

const handleDeletePost = (id: number) => deleteMutation.mutate(id)
Expand Down
Loading

0 comments on commit 6c8bda0

Please sign in to comment.