Adopt TanStack Query
All checks were successful
Deploy to Test Environment / deploy-to-test (push) Successful in 13m46s

This commit is contained in:
2026-01-10 10:43:13 -08:00
parent 6d468544e2
commit dcd9452b8c
31 changed files with 584 additions and 157 deletions

View File

@@ -2,6 +2,7 @@
import { useMutation, useQueryClient } from '@tanstack/react-query';
import * as apiClient from '../../services/apiClient';
import { notifySuccess, notifyError } from '../../services/notificationService';
import { queryKeyBases } from '../../config/queryKeys';
interface AddShoppingListItemParams {
listId: number;
@@ -61,7 +62,7 @@ export const useAddShoppingListItemMutation = () => {
},
onSuccess: () => {
// Invalidate and refetch shopping lists to get the updated list
queryClient.invalidateQueries({ queryKey: ['shopping-lists'] });
queryClient.invalidateQueries({ queryKey: queryKeyBases.shoppingLists });
notifySuccess('Item added to shopping list');
},
onError: (error: Error) => {

View File

@@ -2,6 +2,7 @@
import { useMutation, useQueryClient } from '@tanstack/react-query';
import * as apiClient from '../../services/apiClient';
import { notifySuccess, notifyError } from '../../services/notificationService';
import { queryKeyBases } from '../../config/queryKeys';
interface AddWatchedItemParams {
itemName: string;
@@ -50,7 +51,7 @@ export const useAddWatchedItemMutation = () => {
},
onSuccess: () => {
// Invalidate and refetch watched items to get the updated list
queryClient.invalidateQueries({ queryKey: ['watched-items'] });
queryClient.invalidateQueries({ queryKey: queryKeyBases.watchedItems });
notifySuccess('Item added to watched list');
},
onError: (error: Error) => {

View File

@@ -0,0 +1,113 @@
// src/hooks/mutations/useAuthMutations.ts
import { useMutation } from '@tanstack/react-query';
import * as apiClient from '../../services/apiClient';
import { notifyError } from '../../services/notificationService';
import type { UserProfile } from '../../types';
interface AuthResponse {
userprofile: UserProfile;
token: string;
}
/**
* Mutation hook for user login.
*
* @example
* ```tsx
* const loginMutation = useLoginMutation();
* loginMutation.mutate({ email, password, rememberMe });
* ```
*/
export const useLoginMutation = () => {
return useMutation({
mutationFn: async ({
email,
password,
rememberMe,
}: {
email: string;
password: string;
rememberMe: boolean;
}): Promise<AuthResponse> => {
const response = await apiClient.loginUser(email, password, rememberMe);
if (!response.ok) {
const error = await response.json().catch(() => ({
message: `Request failed with status ${response.status}`,
}));
throw new Error(error.message || 'Failed to login');
}
return response.json();
},
onError: (error: Error) => {
notifyError(error.message || 'Failed to login');
},
});
};
/**
* Mutation hook for user registration.
*
* @example
* ```tsx
* const registerMutation = useRegisterMutation();
* registerMutation.mutate({ email, password, fullName });
* ```
*/
export const useRegisterMutation = () => {
return useMutation({
mutationFn: async ({
email,
password,
fullName,
}: {
email: string;
password: string;
fullName: string;
}): Promise<AuthResponse> => {
const response = await apiClient.registerUser(email, password, fullName, '');
if (!response.ok) {
const error = await response.json().catch(() => ({
message: `Request failed with status ${response.status}`,
}));
throw new Error(error.message || 'Failed to register');
}
return response.json();
},
onError: (error: Error) => {
notifyError(error.message || 'Failed to register');
},
});
};
/**
* Mutation hook for requesting a password reset.
*
* @example
* ```tsx
* const passwordResetMutation = usePasswordResetRequestMutation();
* passwordResetMutation.mutate({ email });
* ```
*/
export const usePasswordResetRequestMutation = () => {
return useMutation({
mutationFn: async ({ email }: { email: string }): Promise<{ message: string }> => {
const response = await apiClient.requestPasswordReset(email);
if (!response.ok) {
const error = await response.json().catch(() => ({
message: `Request failed with status ${response.status}`,
}));
throw new Error(error.message || 'Failed to request password reset');
}
return response.json();
},
onError: (error: Error) => {
notifyError(error.message || 'Failed to request password reset');
},
});
};

View File

@@ -2,6 +2,7 @@
import { useMutation, useQueryClient } from '@tanstack/react-query';
import * as apiClient from '../../services/apiClient';
import { notifySuccess, notifyError } from '../../services/notificationService';
import { queryKeyBases } from '../../config/queryKeys';
interface CreateShoppingListParams {
name: string;
@@ -48,7 +49,7 @@ export const useCreateShoppingListMutation = () => {
},
onSuccess: () => {
// Invalidate and refetch shopping lists to get the updated list
queryClient.invalidateQueries({ queryKey: ['shopping-lists'] });
queryClient.invalidateQueries({ queryKey: queryKeyBases.shoppingLists });
notifySuccess('Shopping list created');
},
onError: (error: Error) => {

View File

@@ -2,6 +2,7 @@
import { useMutation, useQueryClient } from '@tanstack/react-query';
import * as apiClient from '../../services/apiClient';
import { notifySuccess, notifyError } from '../../services/notificationService';
import { queryKeyBases } from '../../config/queryKeys';
interface DeleteShoppingListParams {
listId: number;
@@ -48,7 +49,7 @@ export const useDeleteShoppingListMutation = () => {
},
onSuccess: () => {
// Invalidate and refetch shopping lists to get the updated list
queryClient.invalidateQueries({ queryKey: ['shopping-lists'] });
queryClient.invalidateQueries({ queryKey: queryKeyBases.shoppingLists });
notifySuccess('Shopping list deleted');
},
onError: (error: Error) => {

View File

@@ -0,0 +1,179 @@
// src/hooks/mutations/useProfileMutations.ts
import { useMutation } from '@tanstack/react-query';
import * as apiClient from '../../services/apiClient';
import { notifyError } from '../../services/notificationService';
import type { Profile, Address } from '../../types';
/**
* Mutation hook for updating user profile.
*
* @example
* ```tsx
* const updateProfile = useUpdateProfileMutation();
* updateProfile.mutate({ full_name: 'New Name', avatar_url: 'https://...' });
* ```
*/
export const useUpdateProfileMutation = () => {
return useMutation({
mutationFn: async (data: Partial<Profile>): Promise<Profile> => {
const response = await apiClient.updateUserProfile(data);
if (!response.ok) {
const error = await response.json().catch(() => ({
message: `Request failed with status ${response.status}`,
}));
throw new Error(error.message || 'Failed to update profile');
}
return response.json();
},
onError: (error: Error) => {
notifyError(error.message || 'Failed to update profile');
},
});
};
/**
* Mutation hook for updating user address.
*
* @example
* ```tsx
* const updateAddress = useUpdateAddressMutation();
* updateAddress.mutate({ street_address: '123 Main St', city: 'Toronto' });
* ```
*/
export const useUpdateAddressMutation = () => {
return useMutation({
mutationFn: async (data: Partial<Address>): Promise<Address> => {
const response = await apiClient.updateUserAddress(data);
if (!response.ok) {
const error = await response.json().catch(() => ({
message: `Request failed with status ${response.status}`,
}));
throw new Error(error.message || 'Failed to update address');
}
return response.json();
},
onError: (error: Error) => {
notifyError(error.message || 'Failed to update address');
},
});
};
/**
* Mutation hook for updating user password.
*
* @example
* ```tsx
* const updatePassword = useUpdatePasswordMutation();
* updatePassword.mutate({ password: 'newPassword123' });
* ```
*/
export const useUpdatePasswordMutation = () => {
return useMutation({
mutationFn: async ({ password }: { password: string }): Promise<void> => {
const response = await apiClient.updateUserPassword(password);
if (!response.ok) {
const error = await response.json().catch(() => ({
message: `Request failed with status ${response.status}`,
}));
throw new Error(error.message || 'Failed to update password');
}
return response.json();
},
onError: (error: Error) => {
notifyError(error.message || 'Failed to update password');
},
});
};
/**
* Mutation hook for updating user preferences.
*
* @example
* ```tsx
* const updatePreferences = useUpdatePreferencesMutation();
* updatePreferences.mutate({ darkMode: true });
* ```
*/
export const useUpdatePreferencesMutation = () => {
return useMutation({
mutationFn: async (prefs: Partial<Profile['preferences']>): Promise<Profile> => {
const response = await apiClient.updateUserPreferences(prefs);
if (!response.ok) {
const error = await response.json().catch(() => ({
message: `Request failed with status ${response.status}`,
}));
throw new Error(error.message || 'Failed to update preferences');
}
return response.json();
},
onError: (error: Error) => {
notifyError(error.message || 'Failed to update preferences');
},
});
};
/**
* Mutation hook for exporting user data.
*
* @example
* ```tsx
* const exportData = useExportDataMutation();
* exportData.mutate();
* ```
*/
export const useExportDataMutation = () => {
return useMutation({
mutationFn: async (): Promise<unknown> => {
const response = await apiClient.exportUserData();
if (!response.ok) {
const error = await response.json().catch(() => ({
message: `Request failed with status ${response.status}`,
}));
throw new Error(error.message || 'Failed to export data');
}
return response.json();
},
onError: (error: Error) => {
notifyError(error.message || 'Failed to export data');
},
});
};
/**
* Mutation hook for deleting user account.
*
* @example
* ```tsx
* const deleteAccount = useDeleteAccountMutation();
* deleteAccount.mutate({ password: 'currentPassword' });
* ```
*/
export const useDeleteAccountMutation = () => {
return useMutation({
mutationFn: async ({ password }: { password: string }): Promise<void> => {
const response = await apiClient.deleteUserAccount(password);
if (!response.ok) {
const error = await response.json().catch(() => ({
message: `Request failed with status ${response.status}`,
}));
throw new Error(error.message || 'Failed to delete account');
}
return response.json();
},
onError: (error: Error) => {
notifyError(error.message || 'Failed to delete account');
},
});
};

View File

@@ -2,6 +2,7 @@
import { useMutation, useQueryClient } from '@tanstack/react-query';
import * as apiClient from '../../services/apiClient';
import { notifySuccess, notifyError } from '../../services/notificationService';
import { queryKeyBases } from '../../config/queryKeys';
interface RemoveShoppingListItemParams {
itemId: number;
@@ -48,7 +49,7 @@ export const useRemoveShoppingListItemMutation = () => {
},
onSuccess: () => {
// Invalidate and refetch shopping lists to get the updated list
queryClient.invalidateQueries({ queryKey: ['shopping-lists'] });
queryClient.invalidateQueries({ queryKey: queryKeyBases.shoppingLists });
notifySuccess('Item removed from shopping list');
},
onError: (error: Error) => {

View File

@@ -2,6 +2,7 @@
import { useMutation, useQueryClient } from '@tanstack/react-query';
import * as apiClient from '../../services/apiClient';
import { notifySuccess, notifyError } from '../../services/notificationService';
import { queryKeyBases } from '../../config/queryKeys';
interface RemoveWatchedItemParams {
masterItemId: number;
@@ -48,7 +49,7 @@ export const useRemoveWatchedItemMutation = () => {
},
onSuccess: () => {
// Invalidate and refetch watched items to get the updated list
queryClient.invalidateQueries({ queryKey: ['watched-items'] });
queryClient.invalidateQueries({ queryKey: queryKeyBases.watchedItems });
notifySuccess('Item removed from watched list');
},
onError: (error: Error) => {

View File

@@ -2,6 +2,7 @@
import { useMutation, useQueryClient } from '@tanstack/react-query';
import * as apiClient from '../../services/apiClient';
import { notifySuccess, notifyError } from '../../services/notificationService';
import { queryKeyBases } from '../../config/queryKeys';
import type { ShoppingListItem } from '../../types';
interface UpdateShoppingListItemParams {
@@ -60,7 +61,7 @@ export const useUpdateShoppingListItemMutation = () => {
},
onSuccess: () => {
// Invalidate and refetch shopping lists to get the updated list
queryClient.invalidateQueries({ queryKey: ['shopping-lists'] });
queryClient.invalidateQueries({ queryKey: queryKeyBases.shoppingLists });
notifySuccess('Shopping list item updated');
},
onError: (error: Error) => {