From 6c8bda0c7924192a9d5f5d5093dda52f12a39f8b Mon Sep 17 00:00:00 2001 From: warmachine028 <75939390+warmachine028@users.noreply.github.com> Date: Thu, 24 Oct 2024 22:06:35 +0530 Subject: [PATCH] feat: added hooks --- client/index.html | 4 +- client/src/api/posts.ts | 22 +---- client/src/hooks/index.ts | 131 ++++++++++++++++++++++++++++++ client/src/lib/utils.ts | 2 + client/src/pages/Posts.tsx | 161 ++++--------------------------------- client/src/types/index.ts | 26 ++++++ 6 files changed, 176 insertions(+), 170 deletions(-) diff --git a/client/index.html b/client/index.html index e8d73f7..286bc6a 100644 --- a/client/index.html +++ b/client/index.html @@ -2,9 +2,9 @@ - + - Vite + React + TS + React Query Demo
diff --git a/client/src/api/posts.ts b/client/src/api/posts.ts index 95469e2..96cb51c 100644 --- a/client/src/api/posts.ts +++ b/client/src/api/posts.ts @@ -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' diff --git a/client/src/hooks/index.ts b/client/src/hooks/index.ts index 4bac233..0a1bc0e 100644 --- a/client/src/hooks/index.ts +++ b/client/src/hooks/index.ts @@ -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) @@ -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 + }) +} \ No newline at end of file diff --git a/client/src/lib/utils.ts b/client/src/lib/utils.ts index d1d7f94..94f3f57 100644 --- a/client/src/lib/utils.ts +++ b/client/src/lib/utils.ts @@ -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)) diff --git a/client/src/pages/Posts.tsx b/client/src/pages/Posts.tsx index c858cf8..f5dc8c2 100644 --- a/client/src/pages/Posts.tsx +++ b/client/src/pages/Posts.tsx @@ -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', @@ -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(null) + const [_newPostImage, setNewPostImage] = useState(null) const [editingPost, setEditingPost] = useState(null) const [deletePostId, setDeletePostId] = useState(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() @@ -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) diff --git a/client/src/types/index.ts b/client/src/types/index.ts index 99ce506..a73782c 100644 --- a/client/src/types/index.ts +++ b/client/src/types/index.ts @@ -12,3 +12,29 @@ export type ThemeContextState = { theme: Theme setTheme: (theme: Theme) => void } + +export interface Post { + id: number + title: string + body: string + userId: number + imageUrl: string + tags: string[] + reactions: { + likes: number + dislikes: number + } + views: number +} + +export interface PostPage { + posts: Post[] + nextCursor?: number +} + +export interface PostsResponse { + posts: Post[] + total: number + skip: number + limit: number +}