Skip to content
Prev Previous commit
Next Next commit
fix: atualizar DEALS_VIEW_KEY ao adicionar/remover item do deal
  • Loading branch information
Guilherme Araujo authored and Guilherme Araujo committed Apr 24, 2026
commit 61f74d35a6a3c2af44486346ee1d5ae1f24a467d
16 changes: 16 additions & 0 deletions lib/query/hooks/useDealsQuery.ts
Original file line number Diff line number Diff line change
Expand Up @@ -517,6 +517,14 @@ export const useAddDealItem = () => {
if (error) throw error;
return { dealId, item: data! };
},
onSuccess: (data, { dealId }) => {
queryClient.setQueryData(DEALS_VIEW_KEY, (old) => {
if (!old) return old;
return old.map((d) =>
d.id === dealId ? { ...d, items: [...(d.items ?? []), data.item] } : d
);
});
},
Comment on lines +520 to +527
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

onSettled invalidation cascades to DEALS_VIEW_KEY, undoing this optimization; also missing <DealView[]> generic.

Two concerns with the new direct cache update:

  1. The unchanged onSettled at line 530 calls queryClient.invalidateQueries({ queryKey: queryKeys.deals.lists() }). Because DEALS_VIEW_KEY = [...queryKeys.deals.lists(), 'view'], TanStack's prefix matching will invalidate DEALS_VIEW_KEY as well, triggering an immediate refetch right after this setQueryData. That largely defeats the "optimize deal item cache updates" intent, and it diverges from the pattern used everywhere else in this file (e.g. useUpdateDeal, useDeleteDeal) where the explicit comment is "NÃO fazer invalidateQueries para deals - Realtime gerencia a sincronização".

  2. setQueryData is called without the <DealView[]> generic, so old is inferred as unknown and old.map(...) is not type-safe under strict mode. Every other call site in this file passes the generic explicitly (e.g. lines 272, 299, 363, 487).

Based on learnings: "For Deals entity mutations, always use [...queryKeys.deals.lists(), 'view'] cache key pattern" and "Prefer setQueryData over invalidateQueries for instant UI updates".

♻️ Proposed fix
-    onSuccess: (data, { dealId }) => {
-      queryClient.setQueryData(DEALS_VIEW_KEY, (old) => {
-        if (!old) return old;
-        return old.map((d) =>
-          d.id === dealId ? { ...d, items: [...(d.items ?? []), data.item] } : d
-        );
-      });
-    },
-    onSettled: (_data, _error, { dealId }) => {
-      queryClient.invalidateQueries({ queryKey: queryKeys.deals.detail(dealId) });
-      queryClient.invalidateQueries({ queryKey: queryKeys.deals.lists() });
-    },
+    onSuccess: (data, { dealId }) => {
+      queryClient.setQueryData<DealView[]>(DEALS_VIEW_KEY, (old) => {
+        if (!old) return old;
+        return old.map((d) =>
+          d.id === dealId ? { ...d, items: [...(d.items ?? []), data.item] } : d
+        );
+      });
+    },
+    onSettled: (_data, _error, { dealId }) => {
+      // NÃO invalidar queryKeys.deals.lists() — cascateia para DEALS_VIEW_KEY
+      // e desfaz o setQueryData acima. Realtime mantém a sincronização.
+      queryClient.invalidateQueries({ queryKey: queryKeys.deals.detail(dealId) });
+    },
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
onSuccess: (data, { dealId }) => {
queryClient.setQueryData(DEALS_VIEW_KEY, (old) => {
if (!old) return old;
return old.map((d) =>
d.id === dealId ? { ...d, items: [...(d.items ?? []), data.item] } : d
);
});
},
onSuccess: (data, { dealId }) => {
queryClient.setQueryData<DealView[]>(DEALS_VIEW_KEY, (old) => {
if (!old) return old;
return old.map((d) =>
d.id === dealId ? { ...d, items: [...(d.items ?? []), data.item] } : d
);
});
},
onSettled: (_data, _error, { dealId }) => {
// NÃO invalidar queryKeys.deals.lists() — cascateia para DEALS_VIEW_KEY
// e desfaz o setQueryData acima. Realtime mantém a sincronização.
queryClient.invalidateQueries({ queryKey: queryKeys.deals.detail(dealId) });
},
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/query/hooks/useDealsQuery.ts` around lines 520 - 527, The direct cache
update in the onSuccess handler for adding an item uses queryClient.setQueryData
without the <DealView[]> generic (so old is inferred as unknown) and is
immediately undone because the existing onSettled still calls
queryClient.invalidateQueries({ queryKey: queryKeys.deals.lists() }) which
prefix-invalidates DEALS_VIEW_KEY; fix by adding the explicit generic to
setQueryData (setQueryData<DealView[]>) and stop invalidating the parent lists
key in the onSettled for this mutation (either remove or narrow the
invalidateQueries call so it does not target queryKeys.deals.lists()) so the
optimized in-place update to DEALS_VIEW_KEY persists and remains type-safe.

onSettled: (_data, _error, { dealId }) => {
queryClient.invalidateQueries({ queryKey: queryKeys.deals.detail(dealId) });
queryClient.invalidateQueries({ queryKey: queryKeys.deals.lists() });
Expand All @@ -536,6 +544,14 @@ export const useRemoveDealItem = () => {
if (error) throw error;
return { dealId, itemId };
},
onSuccess: (data) => {
queryClient.setQueryData(DEALS_VIEW_KEY, (old) => {
if (!old) return old;
return old.map((d) =>
d.id === data.dealId ? { ...d, items: (d.items ?? []).filter((i) => i.id !== data.itemId) } : d
);
});
},
Comment on lines +547 to +554
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Same cascade-invalidation and missing-generic issues as useAddDealItem.

The onSettled at line 557 invalidates queryKeys.deals.lists(), which prefix-matches DEALS_VIEW_KEY and triggers a refetch immediately after the optimization here. Also missing <DealView[]> generic on setQueryData, leaving old as unknown.

Based on learnings: "For Deals entity mutations, always use [...queryKeys.deals.lists(), 'view'] cache key pattern" and "Prefer setQueryData over invalidateQueries for instant UI updates".

♻️ Proposed fix
-    onSuccess: (data) => {
-      queryClient.setQueryData(DEALS_VIEW_KEY, (old) => {
-        if (!old) return old;
-        return old.map((d) =>
-          d.id === data.dealId ? { ...d, items: (d.items ?? []).filter((i) => i.id !== data.itemId) } : d
-        );
-      });
-    },
-    onSettled: (_data, _error, { dealId }) => {
-      queryClient.invalidateQueries({ queryKey: queryKeys.deals.detail(dealId) });
-      queryClient.invalidateQueries({ queryKey: queryKeys.deals.lists() });
-    },
+    onSuccess: (data) => {
+      queryClient.setQueryData<DealView[]>(DEALS_VIEW_KEY, (old) => {
+        if (!old) return old;
+        return old.map((d) =>
+          d.id === data.dealId
+            ? { ...d, items: (d.items ?? []).filter((i) => i.id !== data.itemId) }
+            : d
+        );
+      });
+    },
+    onSettled: (_data, _error, { dealId }) => {
+      // Mesmo motivo de useAddDealItem: lists() cascateia em DEALS_VIEW_KEY.
+      queryClient.invalidateQueries({ queryKey: queryKeys.deals.detail(dealId) });
+    },
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
onSuccess: (data) => {
queryClient.setQueryData(DEALS_VIEW_KEY, (old) => {
if (!old) return old;
return old.map((d) =>
d.id === data.dealId ? { ...d, items: (d.items ?? []).filter((i) => i.id !== data.itemId) } : d
);
});
},
onSuccess: (data) => {
queryClient.setQueryData<DealView[]>(DEALS_VIEW_KEY, (old) => {
if (!old) return old;
return old.map((d) =>
d.id === data.dealId
? { ...d, items: (d.items ?? []).filter((i) => i.id !== data.itemId) }
: d
);
});
},
onSettled: (_data, _error, { dealId }) => {
// Mesmo motivo de useAddDealItem: lists() cascateia em DEALS_VIEW_KEY.
queryClient.invalidateQueries({ queryKey: queryKeys.deals.detail(dealId) });
},
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/query/hooks/useDealsQuery.ts` around lines 547 - 554, The current
onSuccess handler updates the DEALS_VIEW_KEY optimistically but uses
setQueryData without a generic (leaving old as unknown) and then onSettled calls
invalidateQueries(queryKeys.deals.lists()) which prefix-matches and immediately
refetches, negating the optimization; fix by changing setQueryData to use the
explicit generic setQueryData<DealView[]> (so old is typed) and update only the
specific view cache key pattern [...queryKeys.deals.lists(), 'view'] (i.e. use
queryClient.setQueryData<DealView[]>(DEALS_VIEW_KEY, ...) where DEALS_VIEW_KEY
is the view key), and change the onSettled invalidation to invalidate only
[...queryKeys.deals.lists(), 'view'] (or remove the invalidate if you prefer
relying on setQueryData) so there is no cascade invalidation from
queryKeys.deals.lists().

onSettled: (_data, _error, { dealId }) => {
queryClient.invalidateQueries({ queryKey: queryKeys.deals.detail(dealId) });
queryClient.invalidateQueries({ queryKey: queryKeys.deals.lists() });
Expand Down