refactor: enhance cache invalidation logic across banking API routes and components for improved data consistency and performance

This commit is contained in:
Julien Froidefond
2025-12-08 14:04:12 +01:00
parent 53bae084c4
commit 8d947ad70f
14 changed files with 412 additions and 200 deletions

View File

@@ -5,17 +5,16 @@ import { useQueryClient } from "@tanstack/react-query";
import type { Transaction } from "@/lib/types";
import { getTransactionsQueryKey } from "@/lib/hooks";
import type { TransactionsPaginatedParams } from "@/services/banking.service";
import { invalidateAllTransactionQueries } from "@/lib/cache-utils";
interface UseTransactionMutationsProps {
transactionParams: TransactionsPaginatedParams;
transactionsData: { transactions: Transaction[]; total: number } | undefined;
invalidateTransactions: () => void;
}
export function useTransactionMutations({
transactionParams,
transactionsData,
invalidateTransactions,
}: UseTransactionMutationsProps) {
const queryClient = useQueryClient();
const [updatingTransactionIds, setUpdatingTransactionIds] = useState<
@@ -64,21 +63,19 @@ export function useTransactionMutations({
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
// TOUJOURS revalider après succès pour garantir la cohérence
invalidateAllTransactionQueries(queryClient);
} catch (error) {
console.error("Failed to update transaction:", error);
// Rollback on error
if (previousData) {
queryClient.setQueryData(queryKey, previousData);
}
invalidateTransactions();
invalidateAllTransactionQueries(queryClient);
}
},
[
transactionsData,
transactionParams,
queryClient,
invalidateTransactions,
]
[transactionsData, transactionParams, queryClient]
);
const markReconciled = useCallback(
@@ -120,21 +117,19 @@ export function useTransactionMutations({
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
// TOUJOURS revalider après succès pour garantir la cohérence
invalidateAllTransactionQueries(queryClient);
} catch (error) {
console.error("Failed to update transaction:", error);
// Rollback on error
if (previousData) {
queryClient.setQueryData(queryKey, previousData);
}
invalidateTransactions();
invalidateAllTransactionQueries(queryClient);
}
},
[
transactionsData,
transactionParams,
queryClient,
invalidateTransactions,
]
[transactionsData, transactionParams, queryClient]
);
const setCategory = useCallback(
@@ -148,6 +143,22 @@ export function useTransactionMutations({
setUpdatingTransactionIds((prev) => new Set(prev).add(transactionId));
// Optimistic cache update
const queryKey = getTransactionsQueryKey(transactionParams);
const previousData =
queryClient.getQueryData<typeof transactionsData>(queryKey);
queryClient.setQueryData<typeof transactionsData>(queryKey, (oldData) => {
if (!oldData) return oldData;
return {
...oldData,
transactions: oldData.transactions.map((t) =>
t.id === transactionId ? { ...t, categoryId } : t
),
};
});
try {
const response = await fetch("/api/banking/transactions", {
method: "PUT",
@@ -159,21 +170,15 @@ export function useTransactionMutations({
throw new Error(`HTTP error! status: ${response.status}`);
}
// Optimistic cache update
const queryKey = getTransactionsQueryKey(transactionParams);
queryClient.setQueryData<typeof transactionsData>(queryKey, (oldData) => {
if (!oldData) return oldData;
return {
...oldData,
transactions: oldData.transactions.map((t) =>
t.id === transactionId ? { ...t, categoryId } : t
),
};
});
// TOUJOURS revalider après succès pour garantir la cohérence
invalidateAllTransactionQueries(queryClient);
} catch (error) {
console.error("Failed to update transaction:", error);
invalidateTransactions();
// Rollback on error
if (previousData) {
queryClient.setQueryData(queryKey, previousData);
}
invalidateAllTransactionQueries(queryClient);
} finally {
setUpdatingTransactionIds((prev) => {
const next = new Set(prev);
@@ -182,7 +187,7 @@ export function useTransactionMutations({
});
}
},
[transactionsData, transactionParams, queryClient, invalidateTransactions]
[transactionsData, transactionParams, queryClient]
);
const deleteTransaction = useCallback(
@@ -217,22 +222,23 @@ export function useTransactionMutations({
if (!response.ok) {
const errorData = await response.json().catch(() => ({}));
throw new Error(
errorData.error || `Failed to delete transaction: ${response.status}`
errorData.error ||
`Failed to delete transaction: ${response.status}`
);
}
// Invalidate related queries
queryClient.invalidateQueries({ queryKey: ["banking-metadata"] });
// TOUJOURS revalider après succès pour garantir la cohérence
invalidateAllTransactionQueries(queryClient);
} catch (error) {
console.error("Failed to delete transaction:", error);
// Rollback on error
if (previousData) {
queryClient.setQueryData(queryKey, previousData);
}
invalidateTransactions();
invalidateAllTransactionQueries(queryClient);
}
},
[transactionsData, transactionParams, queryClient, invalidateTransactions]
[transactionsData, transactionParams, queryClient]
);
const bulkReconcile = useCallback(
@@ -272,21 +278,19 @@ export function useTransactionMutations({
})
)
);
// TOUJOURS revalider après succès pour garantir la cohérence
invalidateAllTransactionQueries(queryClient);
} catch (error) {
console.error("Failed to update transactions:", error);
// Rollback on error
if (previousData) {
queryClient.setQueryData(queryKey, previousData);
}
invalidateTransactions();
invalidateAllTransactionQueries(queryClient);
}
},
[
transactionsData,
transactionParams,
queryClient,
invalidateTransactions,
]
[transactionsData, transactionParams, queryClient]
);
const bulkSetCategory = useCallback(
@@ -304,6 +308,21 @@ export function useTransactionMutations({
return next;
});
// Optimistic cache update
const queryKey = getTransactionsQueryKey(transactionParams);
const previousData =
queryClient.getQueryData<typeof transactionsData>(queryKey);
queryClient.setQueryData<typeof transactionsData>(queryKey, (oldData) => {
if (!oldData) return oldData;
return {
...oldData,
transactions: oldData.transactions.map((t) =>
transactionIds.includes(t.id) ? { ...t, categoryId } : t
),
};
});
try {
await Promise.all(
transactionsToUpdate.map((t) =>
@@ -315,20 +334,15 @@ export function useTransactionMutations({
)
);
// Optimistic cache update
const queryKey = getTransactionsQueryKey(transactionParams);
queryClient.setQueryData<typeof transactionsData>(queryKey, (oldData) => {
if (!oldData) return oldData;
return {
...oldData,
transactions: oldData.transactions.map((t) =>
transactionIds.includes(t.id) ? { ...t, categoryId } : t
),
};
});
// TOUJOURS revalider après succès pour garantir la cohérence
invalidateAllTransactionQueries(queryClient);
} catch (error) {
console.error("Failed to update transactions:", error);
invalidateTransactions();
// Rollback on error
if (previousData) {
queryClient.setQueryData(queryKey, previousData);
}
invalidateAllTransactionQueries(queryClient);
} finally {
setUpdatingTransactionIds((prev) => {
const next = new Set(prev);
@@ -337,7 +351,7 @@ export function useTransactionMutations({
});
}
},
[transactionsData, transactionParams, queryClient, invalidateTransactions]
[transactionsData, transactionParams, queryClient]
);
return {
@@ -350,4 +364,3 @@ export function useTransactionMutations({
updatingTransactionIds,
};
}

View File

@@ -8,6 +8,10 @@ import {
normalizeDescription,
suggestKeyword,
} from "@/components/rules/constants";
import {
invalidateAllTransactionQueries,
invalidateAllCategoryQueries,
} from "@/lib/cache-utils";
interface UseTransactionRulesProps {
transactionsData: { transactions: Transaction[] } | undefined;
@@ -94,9 +98,9 @@ export function useTransactionRules({
);
}
// Invalidate queries
queryClient.invalidateQueries({ queryKey: ["transactions"] });
queryClient.invalidateQueries({ queryKey: ["banking-metadata"] });
// Invalider toutes les queries liées
invalidateAllTransactionQueries(queryClient);
invalidateAllCategoryQueries(queryClient);
setRuleDialogOpen(false);
},
[metadata, queryClient]
@@ -110,4 +114,3 @@ export function useTransactionRules({
handleSaveRule,
};
}