eletrotupi / tcc/ frontend/hooks/useMoodEntries.queries.ts1b634a75f70686cdc5553604c0e35c6f4009df61
3.6 KB | 130 lines Raw
1
import {
2
  useQuery,
3
  useMutation,
4
  useQueryClient,
5
  useInfiniteQuery,
6
} from "@tanstack/react-query";
7

8
import { apiClient, MoodEntry, CreateMoodEntryPayload } from "@/lib/api";
9

10
export const moodKeys = {
11
  all: () => ["mood-entries"] as const,
12
  lists: () => [...moodKeys.all(), "list"] as const,
13
  list: (filters?: MoodEntryFilters) => [...moodKeys.lists(), filters] as const,
14
  detail: (id: string) => [...moodKeys.all(), "detail", id] as const,
15
};
16

17
interface MoodEntryFilters {
18
  from?: string; // ISO date
19
  to?: string; // ISO date
20
  limit?: number;
21
}
22

23
// Queries
24

25
// Fetch a paginated list of mood entries.
26
// On cold start, returns cached data instantly then revalidates.
27
export const useMoodEntries = (filters?: MoodEntryFilters) => {
28
  return useQuery({
29
    queryKey: moodKeys.list(filters),
30
    queryFn: () => apiClient.getMoodEntries(filters),
31
  });
32
};
33

34
// Infinite scroll version
35
export const useMoodEntriesInfinite = (limit = 20) => {
36
  return useInfiniteQuery({
37
    queryKey: [...moodKeys.lists(), "infinite"],
38
    queryFn: ({ pageParam = 1 }) =>
39
      apiClient.getMoodEntries({ page: pageParam, limit }),
40
    getNextPageParam: (lastPage) => lastPage.nextPage ?? undefined,
41
    initialPageParam: 1,
42
  });
43
};
44

45
// Single entry detail
46
export const useMoodEntry = (id: string) => {
47
  return useQuery({
48
    queryKey: moodKeys.detail(id),
49
    queryFn: () => apiClient.getMoodEntry(id),
50
    enabled: !!id,
51
  });
52
};
53

54
// MUTATIONS
55

56
// Create a new mood entry
57
// Optimistically adds to cache, rolls back on failure
58
export const useCreateMoodEntry = () => {
59
  const queryClient = useQueryClient();
60

61
  return useMutation({
62
    mutationFn: (payload: CreateMoodEntryPayload) =>
63
      apiClient.createMoodEntry(payload),
64

65
    // Optimistic update so that the UI reflects the new entry immediately
66
    onMutate: async (payload) => {
67
      // Cancel any in-flight refetches so they don't overwrite our optimistic update
68
      await queryClient.cancelQueries({ queryKey: moodKeys.lists() });
69

70
      // Snapshot current cache so we can roll back
71
      const previous = queryClient.getQueryData(moodKeys.list());
72

73
      // Inject a temporary optimistic record
74
      // TODO: might as well create a shared attrs that we're keeping it
75
      queryClient.setQueryData(moodKeys.list(), (old: MoodEntry[] = []) => [
76
        {
77
          ...payload,
78
          id: `temp-${Date.now()}`,
79
          createdAt: new Date().toISOString(),
80
          _optimistic: true,
81
        },
82
        ...old,
83
      ]);
84

85
      return { previous };
86
    },
87

88
    // On error, roll back to the snapshot
89
    onError: (_err, _payload, context) => {
90
      if (context?.previous) {
91
        queryClient.setQueryData(moodKeys.list(), context.previous);
92
      }
93
    },
94

95
    // On success, replace optimistic record with real one from server
96
    onSuccess: () => {
97
      queryClient.invalidateQueries({ queryKey: moodKeys.lists() });
98
    },
99
  });
100
};
101

102
// Delete a mood entry with optimistic removal
103
export const useDeleteMoodEntry = () => {
104
  const queryClient = useQueryClient();
105

106
  return useMutation({
107
    mutationFn: (id: string) => apiClient.deleteMoodEntry(id),
108

109
    onMutate: async (id) => {
110
      await queryClient.cancelQueries({ queryKey: moodKeys.lists() });
111
      const previous = queryClient.getQueryData(moodKeys.list());
112

113
      queryClient.setQueryData(moodKeys.list(), (old: MoodEntry[] = []) =>
114
        old.filter((e) => e.id !== Number(id)),
115
      );
116

117
      return { previous };
118
    },
119

120
    onError: (_err, _id, context) => {
121
      if (context?.previous) {
122
        queryClient.setQueryData(moodKeys.list(), context.previous);
123
      }
124
    },
125

126
    onSuccess: () => {
127
      queryClient.invalidateQueries({ queryKey: moodKeys.lists() });
128
    },
129
  });
130
};