Some checks failed
Deploy to Test Environment / deploy-to-test (push) Failing after 46s
322 lines
11 KiB
Markdown
322 lines
11 KiB
Markdown
# ADR-0005 Phase 3 Implementation Summary
|
|
|
|
**Date**: 2026-01-08
|
|
**Status**: ✅ Complete
|
|
|
|
## Overview
|
|
|
|
Successfully completed Phase 3 of ADR-0005 enforcement by creating all mutation hooks for data modifications using TanStack Query mutations.
|
|
|
|
## Files Created
|
|
|
|
### Mutation Hooks
|
|
|
|
All mutation hooks follow a consistent pattern:
|
|
- Automatic cache invalidation via `queryClient.invalidateQueries()`
|
|
- Success/error notifications via notification service
|
|
- Proper TypeScript types for parameters
|
|
- Comprehensive JSDoc documentation with examples
|
|
|
|
#### Watched Items Mutations
|
|
|
|
1. **[src/hooks/mutations/useAddWatchedItemMutation.ts](../src/hooks/mutations/useAddWatchedItemMutation.ts)**
|
|
- Adds an item to the user's watched items list
|
|
- Parameters: `{ itemName: string, category?: string }`
|
|
- Invalidates: `['watched-items']` query
|
|
|
|
2. **[src/hooks/mutations/useRemoveWatchedItemMutation.ts](../src/hooks/mutations/useRemoveWatchedItemMutation.ts)**
|
|
- Removes an item from the user's watched items list
|
|
- Parameters: `{ masterItemId: number }`
|
|
- Invalidates: `['watched-items']` query
|
|
|
|
#### Shopping List Mutations
|
|
|
|
3. **[src/hooks/mutations/useCreateShoppingListMutation.ts](../src/hooks/mutations/useCreateShoppingListMutation.ts)**
|
|
- Creates a new shopping list
|
|
- Parameters: `{ name: string }`
|
|
- Invalidates: `['shopping-lists']` query
|
|
|
|
4. **[src/hooks/mutations/useDeleteShoppingListMutation.ts](../src/hooks/mutations/useDeleteShoppingListMutation.ts)**
|
|
- Deletes an entire shopping list
|
|
- Parameters: `{ listId: number }`
|
|
- Invalidates: `['shopping-lists']` query
|
|
|
|
5. **[src/hooks/mutations/useAddShoppingListItemMutation.ts](../src/hooks/mutations/useAddShoppingListItemMutation.ts)**
|
|
- Adds an item to a shopping list
|
|
- Parameters: `{ listId: number, item: { masterItemId?: number, customItemName?: string } }`
|
|
- Supports both master items and custom items
|
|
- Invalidates: `['shopping-lists']` query
|
|
|
|
6. **[src/hooks/mutations/useUpdateShoppingListItemMutation.ts](../src/hooks/mutations/useUpdateShoppingListItemMutation.ts)**
|
|
- Updates a shopping list item (quantity, notes, purchased status)
|
|
- Parameters: `{ itemId: number, updates: Partial<ShoppingListItem> }`
|
|
- Updatable fields: `custom_item_name`, `quantity`, `is_purchased`, `notes`
|
|
- Invalidates: `['shopping-lists']` query
|
|
|
|
7. **[src/hooks/mutations/useRemoveShoppingListItemMutation.ts](../src/hooks/mutations/useRemoveShoppingListItemMutation.ts)**
|
|
- Removes an item from a shopping list
|
|
- Parameters: `{ itemId: number }`
|
|
- Invalidates: `['shopping-lists']` query
|
|
|
|
#### Barrel Export
|
|
|
|
8. **[src/hooks/mutations/index.ts](../src/hooks/mutations/index.ts)**
|
|
- Centralized export for all mutation hooks
|
|
- Easy imports: `import { useAddWatchedItemMutation } from '../hooks/mutations'`
|
|
|
|
## Mutation Hook Pattern
|
|
|
|
All mutation hooks follow this consistent structure:
|
|
|
|
```typescript
|
|
export const useSomeMutation = () => {
|
|
const queryClient = useQueryClient();
|
|
|
|
return useMutation({
|
|
mutationFn: async (params) => {
|
|
const response = await apiClient.someMethod(params);
|
|
|
|
if (!response.ok) {
|
|
const error = await response.json().catch(() => ({
|
|
message: `Request failed with status ${response.status}`,
|
|
}));
|
|
throw new Error(error.message || 'Failed to perform action');
|
|
}
|
|
|
|
return response.json();
|
|
},
|
|
onSuccess: () => {
|
|
// Invalidate affected queries
|
|
queryClient.invalidateQueries({ queryKey: ['some-query'] });
|
|
notifySuccess('Action completed successfully');
|
|
},
|
|
onError: (error: Error) => {
|
|
notifyError(error.message || 'Failed to perform action');
|
|
},
|
|
});
|
|
};
|
|
```
|
|
|
|
## Usage Examples
|
|
|
|
### Adding a Watched Item
|
|
|
|
```tsx
|
|
import { useAddWatchedItemMutation } from '../hooks/mutations';
|
|
|
|
function WatchedItemsManager() {
|
|
const addWatchedItem = useAddWatchedItemMutation();
|
|
|
|
const handleAdd = () => {
|
|
addWatchedItem.mutate(
|
|
{ itemName: 'Milk', category: 'Dairy' },
|
|
{
|
|
onSuccess: () => console.log('Added to watched list!'),
|
|
onError: (error) => console.error('Failed:', error),
|
|
}
|
|
);
|
|
};
|
|
|
|
return (
|
|
<button
|
|
onClick={handleAdd}
|
|
disabled={addWatchedItem.isPending}
|
|
>
|
|
{addWatchedItem.isPending ? 'Adding...' : 'Add to Watched List'}
|
|
</button>
|
|
);
|
|
}
|
|
```
|
|
|
|
### Managing Shopping Lists
|
|
|
|
```tsx
|
|
import {
|
|
useCreateShoppingListMutation,
|
|
useAddShoppingListItemMutation,
|
|
useUpdateShoppingListItemMutation
|
|
} from '../hooks/mutations';
|
|
|
|
function ShoppingListManager() {
|
|
const createList = useCreateShoppingListMutation();
|
|
const addItem = useAddShoppingListItemMutation();
|
|
const updateItem = useUpdateShoppingListItemMutation();
|
|
|
|
const handleCreateList = () => {
|
|
createList.mutate({ name: 'Weekly Groceries' });
|
|
};
|
|
|
|
const handleAddItem = (listId: number, masterItemId: number) => {
|
|
addItem.mutate({
|
|
listId,
|
|
item: { masterItemId }
|
|
});
|
|
};
|
|
|
|
const handleMarkPurchased = (itemId: number) => {
|
|
updateItem.mutate({
|
|
itemId,
|
|
updates: { is_purchased: true }
|
|
});
|
|
};
|
|
|
|
return (
|
|
<div>
|
|
<button onClick={handleCreateList}>Create List</button>
|
|
{/* ... other UI */}
|
|
</div>
|
|
);
|
|
}
|
|
```
|
|
|
|
## Benefits Achieved
|
|
|
|
### Performance
|
|
- ✅ **Automatic cache updates** - Queries automatically refetch after mutations
|
|
- ✅ **Request deduplication** - Multiple mutation calls are properly queued
|
|
- ✅ **Optimistic updates ready** - Infrastructure in place for Phase 4
|
|
|
|
### Code Quality
|
|
- ✅ **Standardized pattern** - All mutations follow the same structure
|
|
- ✅ **Comprehensive documentation** - JSDoc with examples for every hook
|
|
- ✅ **Type safety** - Full TypeScript types for all parameters
|
|
- ✅ **Error handling** - Consistent error handling and user notifications
|
|
|
|
### Developer Experience
|
|
- ✅ **React Query Devtools** - Inspect mutation states in real-time
|
|
- ✅ **Easy imports** - Barrel export for clean imports
|
|
- ✅ **Consistent API** - Same pattern across all mutations
|
|
- ✅ **Built-in loading states** - `isPending`, `isError`, `isSuccess` states
|
|
|
|
### User Experience
|
|
- ✅ **Automatic notifications** - Success/error toasts on all mutations
|
|
- ✅ **Fresh data** - Queries automatically update after mutations
|
|
- ✅ **Loading states** - UI can show loading indicators during mutations
|
|
- ✅ **Error feedback** - Clear error messages on failures
|
|
|
|
## Current State
|
|
|
|
### Completed
|
|
- ✅ All 7 mutation hooks created
|
|
- ✅ Barrel export created for easy imports
|
|
- ✅ Comprehensive documentation with examples
|
|
- ✅ Consistent error handling and notifications
|
|
- ✅ Automatic cache invalidation on all mutations
|
|
|
|
### Not Yet Migrated
|
|
|
|
The following custom hooks still use the old `useApi` pattern with manual state management:
|
|
|
|
1. **[src/hooks/useWatchedItems.tsx](../src/hooks/useWatchedItems.tsx)** (74 lines)
|
|
- Uses `useApi` for add/remove operations
|
|
- Manually updates state via `setWatchedItems`
|
|
- Should be refactored to use mutation hooks
|
|
|
|
2. **[src/hooks/useShoppingLists.tsx](../src/hooks/useShoppingLists.tsx)** (222 lines)
|
|
- Uses `useApi` for all CRUD operations
|
|
- Manually updates state via `setShoppingLists`
|
|
- Complex manual state synchronization logic
|
|
- Should be refactored to use mutation hooks
|
|
|
|
These hooks are actively used throughout the application and will need careful refactoring in Phase 4.
|
|
|
|
## Remaining Work
|
|
|
|
### Phase 4: Hook Refactoring & Cleanup
|
|
|
|
#### Step 1: Refactor useWatchedItems
|
|
- [ ] Replace `useApi` calls with mutation hooks
|
|
- [ ] Remove manual state management logic
|
|
- [ ] Simplify to just wrap mutation hooks with custom logic
|
|
- [ ] Update all tests
|
|
|
|
#### Step 2: Refactor useShoppingLists
|
|
- [ ] Replace `useApi` calls with mutation hooks
|
|
- [ ] Remove manual state management logic
|
|
- [ ] Remove complex state synchronization
|
|
- [ ] Keep `activeListId` state (still needed)
|
|
- [ ] Update all tests
|
|
|
|
#### Step 3: Remove Deprecated Code
|
|
- [ ] Remove `setWatchedItems` from UserDataContext
|
|
- [ ] Remove `setShoppingLists` from UserDataContext
|
|
- [ ] Remove `useApi` hook (if no longer used)
|
|
- [ ] Remove `useApiOnMount` hook (already deprecated)
|
|
|
|
#### Step 4: Add Optimistic Updates (Optional)
|
|
- [ ] Implement optimistic updates for better UX
|
|
- [ ] Use `onMutate` to update cache before server response
|
|
- [ ] Implement rollback on error
|
|
|
|
#### Step 5: Documentation & Testing
|
|
- [ ] Update all component documentation
|
|
- [ ] Update developer onboarding guide
|
|
- [ ] Add integration tests for mutation flows
|
|
- [ ] Create migration guide for other developers
|
|
|
|
## Testing Recommendations
|
|
|
|
Before considering Phase 4:
|
|
|
|
1. **Manual Testing**
|
|
- Add/remove watched items
|
|
- Create/delete shopping lists
|
|
- Add/remove/update shopping list items
|
|
- Verify cache updates correctly
|
|
- Check success/error notifications
|
|
|
|
2. **React Query Devtools**
|
|
- Open devtools in development
|
|
- Watch mutations execute
|
|
- Verify cache invalidation
|
|
- Check mutation states (pending, success, error)
|
|
|
|
3. **Network Tab**
|
|
- Verify API calls are correct
|
|
- Check request/response payloads
|
|
- Ensure no duplicate requests
|
|
|
|
4. **Error Scenarios**
|
|
- Test with network offline
|
|
- Test with invalid data
|
|
- Verify error notifications appear
|
|
- Check cache remains consistent
|
|
|
|
## Migration Path for Components
|
|
|
|
Components currently using `useWatchedItems` or `useShoppingLists` can continue using them as-is. When we refactor those hooks in Phase 4, the component interface will remain the same.
|
|
|
|
For new components, you can use mutation hooks directly:
|
|
|
|
```tsx
|
|
// Old way (still works)
|
|
import { useWatchedItems } from '../hooks/useWatchedItems';
|
|
|
|
function MyComponent() {
|
|
const { addWatchedItem, removeWatchedItem } = useWatchedItems();
|
|
// ...
|
|
}
|
|
|
|
// New way (recommended for new code)
|
|
import { useAddWatchedItemMutation, useRemoveWatchedItemMutation } from '../hooks/mutations';
|
|
|
|
function MyComponent() {
|
|
const addWatchedItem = useAddWatchedItemMutation();
|
|
const removeWatchedItem = useRemoveWatchedItemMutation();
|
|
// ...
|
|
}
|
|
```
|
|
|
|
## Documentation Updates
|
|
|
|
- [x] Created [Phase 3 Summary](./adr-0005-phase-3-summary.md)
|
|
- [ ] Update [ADR-0005](../docs/adr/0005-frontend-state-management-and-server-cache-strategy.md) (mark Phase 3 complete)
|
|
- [ ] Update component documentation (Phase 4)
|
|
- [ ] Update developer onboarding guide (Phase 4)
|
|
|
|
## Conclusion
|
|
|
|
Phase 3 successfully created all mutation hooks following TanStack Query best practices. The application now has a complete set of standardized mutation operations with automatic cache invalidation and user notifications.
|
|
|
|
**Next Steps**: Proceed to Phase 4 to refactor existing custom hooks (`useWatchedItems` and `useShoppingLists`) to use the new mutation hooks, then remove deprecated state setters and cleanup old code.
|