From 5dd249fa1e3b324d57fb9af7e68505c01e57de51 Mon Sep 17 00:00:00 2001 From: warmachine028 <75939390+warmachine028@users.noreply.github.com> Date: Fri, 25 Oct 2024 04:22:44 +0530 Subject: [PATCH] feat: zustand, react-query --- client/bun.lockb | Bin 146995 -> 147472 bytes client/package.json | 3 +- client/src/api/posts.ts | 16 ++-- client/src/components/AppRouter.tsx | 1 + client/src/hooks/index.ts | 138 ++++++++++++++++------------ client/src/pages/Posts.tsx | 19 ++-- client/src/store/index.ts | 13 +++ 7 files changed, 118 insertions(+), 72 deletions(-) create mode 100644 client/src/store/index.ts diff --git a/client/bun.lockb b/client/bun.lockb index 7c072754ac56f538ece832422c54d3e8ab0de3b9..bfe07c16895d77139d2071b7f5c51615aa60e433 100644 GIT binary patch delta 25720 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 delta 25237 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 diff --git a/client/package.json b/client/package.json index 3738b52..76ace7a 100644 --- a/client/package.json +++ b/client/package.json @@ -27,7 +27,8 @@ "react-intersection-observer": "^9.13.1", "react-router-dom": "^6.27.0", "tailwind-merge": "^2.5.4", - "tailwindcss-animate": "^1.0.7" + "tailwindcss-animate": "^1.0.7", + "zustand": "^5.0.0" }, "devDependencies": { "@eslint/js": "^9.13.0", diff --git a/client/src/api/posts.ts b/client/src/api/posts.ts index bf5ee2f..1be2706 100644 --- a/client/src/api/posts.ts +++ b/client/src/api/posts.ts @@ -1,11 +1,13 @@ import axios from 'axios' import { PostsResponse, Post } from '@/types' -const API_URL = import.meta.env.VITE_API_URL +const baseURL = import.meta.env.VITE_API_URL + +const api = axios.create({ baseURL }) export const getPosts = async (skip: number = 0, limit: number = 10): Promise => { try { - const { data } = await axios.get(`${API_URL}/posts?limit=${limit}&skip=${skip}`) + const { data } = await api.get(`/posts?limit=${limit}&skip=${skip}`) // Add imageUrl and transform reactions to the required format for each post const postsWithImages = data.posts.map((post) => ({ @@ -25,7 +27,7 @@ export const getPosts = async (skip: number = 0, limit: number = 10): Promise): Promise => { try { - const { data } = await axios.post(`${API_URL}/posts/add`, post) + const { data } = await api.post(`/posts/add`, post) return { ...data, imageUrl: `https://picsum.photos/seed/${data.id}/800/600`, @@ -39,7 +41,7 @@ export const createPost = async (post: Omit): export const updatePost = async (post: Partial & { id: number }): Promise => { try { - const { data } = await axios.put(`${API_URL}/posts/${post.id}`, post) + const { data } = await api.put(`/posts/${post.id}`, post) return { ...data, imageUrl: `https://picsum.photos/seed/${data.id}/800/600`, @@ -54,7 +56,7 @@ export const updatePost = async (post: Partial & { id: number }): Promise< // Delete a post export const deletePost = async (id: number): Promise => { try { - await axios.delete(`${API_URL}/posts/${id}`) + await api.delete(`/posts/${id}`) } catch (error) { throw handleApiError(error) } @@ -63,7 +65,7 @@ export const deletePost = async (id: number): Promise => { // Search posts export const searchPosts = async (query: string): Promise => { try { - const { data } = await axios.get(`${API_URL}/posts/search?q=${query}`) + const { data } = await api.get(`/posts/search?q=${query}`) const postsWithImages = data.posts.map((post) => ({ ...post, @@ -82,7 +84,7 @@ export const searchPosts = async (query: string): Promise => { // Get posts by user export const getPostsByUser = async (userId: number): Promise => { try { - const { data } = await axios.get(`${API_URL}/posts/user/${userId}`) + const { data } = await api.get(`/posts/user/${userId}`) const postsWithImages = data.posts.map((post) => ({ ...post, diff --git a/client/src/components/AppRouter.tsx b/client/src/components/AppRouter.tsx index 3d0a124..8ca5266 100644 --- a/client/src/components/AppRouter.tsx +++ b/client/src/components/AppRouter.tsx @@ -1,6 +1,7 @@ import { Link, Route, Routes, useLocation } from 'react-router-dom' import { Posts, Todos, Vite } from '@/pages' import { Button } from '@/components/ui' + const AppRouter = () => { const location = useLocation() return ( diff --git a/client/src/hooks/index.ts b/client/src/hooks/index.ts index 0a1bc0e..a1363ca 100644 --- a/client/src/hooks/index.ts +++ b/client/src/hooks/index.ts @@ -1,8 +1,9 @@ -import { PostPage } from '@/types' -import { useContext } from 'react' +import type { Post, PostPage } from '@/types' +import { useContext, useEffect } from 'react' import { ThemeContext } from '@/contexts' import { createPost, deletePost, getPosts, updatePost } from '@/api' import { useInfiniteQuery, useMutation, useQueryClient } from '@tanstack/react-query' +import { useStore } from '@/store' export const useTheme = () => { const context = useContext(ThemeContext) @@ -14,8 +15,34 @@ export const useTheme = () => { return context } +export const useGetPosts = () => { + const { setOptimisticPages } = useStore() + + const query = 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, + }) + + useEffect(() => { + if (query.data?.pages) { + setOptimisticPages(query.data.pages) + } + }, [query.data?.pages, setOptimisticPages]) + + return query +} + export const useCreatePost = () => { - const queryClient = useQueryClient() + const queryClient = useQueryClient() // Use QueryClient directly instead of from store + const { optimisticPages, setOptimisticPages } = useStore() return useMutation({ mutationFn: createPost, @@ -23,46 +50,52 @@ export const useCreatePost = () => { await queryClient.cancelQueries({ queryKey: ['posts'] }) const previousData = queryClient.getQueryData(['posts']) - queryClient.setQueryData<{ pages: PostPage[]; pageParams: number[] }>(['posts'], (old) => { - if (!old) { - return { pages: [], pageParams: [] } + const optimisticPost: Post = { + ...newPost, + id: Date.now(), + imageUrl: `https://picsum.photos/seed/${Date.now()}/800/600`, + views: 0, + reactions: { + likes: 0, + dislikes: 0 } - 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 - ] - } - }) + } + + const updatedPages = [ + { + posts: [optimisticPost], + nextCursor: optimisticPages[0]?.nextCursor + }, + ...optimisticPages + ] + + queryClient.setQueryData<{ pages: PostPage[]; pageParams: number[] }>(['posts'], (old) => ({ + ...(old ?? { pageParams: [] }), + pages: updatedPages + })) + setOptimisticPages(updatedPages) return { previousData } }, onError: (_err, _newPost, context) => { + const previousPages = (context?.previousData as { pages: PostPage[] })?.pages ?? [] queryClient.setQueryData(['posts'], context?.previousData) + setOptimisticPages(previousPages) }, onSuccess: () => { - queryClient.invalidateQueries({ queryKey: ['posts'] }) + // Force a fresh refetch from the server + queryClient.invalidateQueries({ + queryKey: ['posts'], + exact: true, + refetchType: 'all' + }) } }) } export const useUpdatePost = () => { const queryClient = useQueryClient() + const { optimisticPages, setOptimisticPages } = useStore() return useMutation({ mutationFn: updatePost, @@ -70,33 +103,37 @@ export const useUpdatePost = () => { 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 - ) - })) - } - }) + // Update both states + const newPages = optimisticPages.map((page) => ({ + ...page, + posts: page.posts.map((post) => (post.id === updatedPost.id ? { ...post, ...updatedPost } : post)) + })) + + queryClient.setQueryData<{ pages: PostPage[]; pageParams: number[] }>(['posts'], (old) => ({ + ...(old ?? { pageParams: [] }), + pages: newPages + })) + setOptimisticPages(newPages) return { previousData } }, onError: (_err, _updatedPost, context) => { + const previousPages = (context?.previousData as { pages: PostPage[] })?.pages ?? [] queryClient.setQueryData(['posts'], context?.previousData) + setOptimisticPages(previousPages) }, onSuccess: () => { - queryClient.invalidateQueries({ queryKey: ['posts'] }) + queryClient.invalidateQueries({ + queryKey: ['posts'], + refetchType: 'all' + }) } }) } export const useDeletePost = () => { const queryClient = useQueryClient() - + return useMutation({ mutationFn: deletePost, onMutate: async (postId) => { @@ -125,19 +162,4 @@ export const useDeletePost = () => { 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/pages/Posts.tsx b/client/src/pages/Posts.tsx index 3f597a6..cd0718b 100644 --- a/client/src/pages/Posts.tsx +++ b/client/src/pages/Posts.tsx @@ -2,10 +2,11 @@ import { useRef, useState } from 'react' import { useInView } from 'react-intersection-observer' import { Button, Input, Textarea, ScrollArea, Badge } from '@/components/ui' import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card' -import { Loader2, Plus } from 'lucide-react' +import { Loader2, Plus, RefreshCcw } from 'lucide-react' import { motion, AnimatePresence } from 'framer-motion' import { useCreatePost, useGetPosts } from '@/hooks' import { Post } from '@/components' +import { useStore } from '@/store' const CreatePost = () => { const initialData = { @@ -112,21 +113,27 @@ const CreatePost = () => { const Posts = () => { const { ref, inView } = useInView() - const { data, fetchNextPage, hasNextPage, isFetchingNextPage, status } = useGetPosts() + const { optimisticPages } = useStore() + const { data, fetchNextPage, hasNextPage, isFetchingNextPage, status, refetch, isRefetching } = useGetPosts() if (inView && hasNextPage) { fetchNextPage() } - + console.log(data) return (

- Posts +
+ Posts + +
- {data?.pages.flatMap((page) => page.posts).length || 0} posts + {optimisticPages.flatMap((page) => page.posts).length || 0} posts
@@ -145,7 +152,7 @@ const Posts = () => { exit={{ opacity: 0 }} className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6 auto-rows-max" > - {data.pages.flatMap((page) => + {optimisticPages.map((page) => page.posts.map((post) => ) )} diff --git a/client/src/store/index.ts b/client/src/store/index.ts new file mode 100644 index 0000000..dfc5071 --- /dev/null +++ b/client/src/store/index.ts @@ -0,0 +1,13 @@ +import { PostPage } from '@/types' +import { create } from 'zustand' + +// Updated store configuration +interface StoreState { + optimisticPages: PostPage[] + setOptimisticPages: (pages: PostPage[]) => void +} + +export const useStore = create()((set) => ({ + optimisticPages: [], + setOptimisticPages: (pages: PostPage[]) => set({ optimisticPages: pages }) +}))