import { useQuery, useMutation, useQueryClient, useInfiniteQuery, } from '@tanstack/react-query'; import { apiClient, MoodEntry, CreateMoodEntryPayload } from '@/lib/api'; export const moodKeys = { all: () => ['mood-entries'] as const, lists: () => [...moodKeys.all(), 'list'] as const, list: (filters?: MoodEntryFilters) => [...moodKeys.lists(), filters] as const, detail: (id: string) => [...moodKeys.all(), 'detail', id] as const, }; interface MoodEntryFilters { from?: string; // ISO date to?: string; // ISO date limit?: number; } // Queries // Fetch a paginated list of mood entries. // On cold start, returns cached data instantly then revalidates. export const useMoodEntries = (filters?: MoodEntryFilters) => { return useQuery({ queryKey: moodKeys.list(filters), queryFn: () => apiClient.getMoodEntries(filters), }); } // Infinite scroll version export const useMoodEntriesInfinite = (limit = 20) => { return useInfiniteQuery({ queryKey: [...moodKeys.lists(), 'infinite'], queryFn: ({ pageParam = 1 }) => apiClient.getMoodEntries({ page: pageParam, limit }), getNextPageParam: (lastPage) => lastPage.nextPage ?? undefined, initialPageParam: 1, }); } // Single entry detail export const useMoodEntry = (id: string) => { return useQuery({ queryKey: moodKeys.detail(id), queryFn: () => apiClient.getMoodEntry(id), enabled: !!id, }); } // MUTATIONS // Create a new mood entry // Optimistically adds to cache, rolls back on failure export const useCreateMoodEntry = () => { const queryClient = useQueryClient(); return useMutation({ mutationFn: (payload: CreateMoodEntryPayload) => apiClient.createMoodEntry(payload), // Optimistic update so that the UI reflects the new entry immediately onMutate: async (payload) => { // Cancel any in-flight refetches so they don't overwrite our optimistic update await queryClient.cancelQueries({ queryKey: moodKeys.lists() }); // Snapshot current cache so we can roll back const previous = queryClient.getQueryData(moodKeys.list()); // Inject a temporary optimistic record // TODO: might as well create a shared attrs that we're keeping it queryClient.setQueryData(moodKeys.list(), (old: MoodEntry[] = []) => [ { ...payload, id: `temp-${Date.now()}`, createdAt: new Date().toISOString(), _optimistic: true, }, ...old, ]); return { previous }; }, // On error, roll back to the snapshot onError: (_err, _payload, context) => { if (context?.previous) { queryClient.setQueryData(moodKeys.list(), context.previous); } }, // On success, replace optimistic record with real one from server onSuccess: () => { queryClient.invalidateQueries({ queryKey: moodKeys.lists() }); }, }); } // Delete a mood entry with optimistic removal export const useDeleteMoodEntry = () => { const queryClient = useQueryClient(); return useMutation({ mutationFn: (id: string) => apiClient.deleteMoodEntry(id), onMutate: async (id) => { await queryClient.cancelQueries({ queryKey: moodKeys.lists() }); const previous = queryClient.getQueryData(moodKeys.list()); queryClient.setQueryData(moodKeys.list(), (old: MoodEntry[] = []) => old.filter((e) => e.id !== id) ); return { previous }; }, onError: (_err, _id, context) => { if (context?.previous) { queryClient.setQueryData(moodKeys.list(), context.previous); } }, onSuccess: () => { queryClient.invalidateQueries({ queryKey: moodKeys.lists() }); }, }); }