name: generate-store-action description: Generate Zustand async action with loading/error states and toast notifications. Use when adding new API calls to the health store. allowed-tools: Read, Edit, Grep
Generate Store Action
Generate a Zustand async action for the health store with loading/error state management.
Usage
When user requests to add a store action, ask for:
- Action name (e.g., "fetchWaterLogs", "addSleepLog", "updateWeight")
- API endpoint (e.g., "/api/water-intake", "/api/sleep")
- HTTP method (GET, POST, PUT, DELETE)
- Request/response types
- State updates needed (what gets stored in Zustand)
Implementation Pattern
Based on src/lib/store/healthStore.ts existing patterns.
Pattern Structure
Add to existing store in src/lib/store/healthStore.ts:
// In HealthState interface:
interface HealthState {
// ... existing state ...
waterLogs: WaterLog[];
// ... existing actions ...
fetchWaterLogs: (date: string) => Promise<void>;
addWaterLog: (log: Omit<WaterLog, 'id' | 'createdAt'>) => Promise<void>;
deleteWaterLog: (id: string) => Promise<void>;
}
// In the store creation:
export const useHealthStore = create<HealthState>((set, get) => ({
// ... existing state ...
waterLogs: [],
// ... existing actions ...
fetchWaterLogs: async (date: string) => {
set({ isLoading: true, error: null });
try {
const response = await fetch(`/api/water-intake?date=${date}`);
if (!response.ok) throw new Error('Failed to fetch water logs');
const data = await response.json();
set({ waterLogs: data, isLoading: false });
} catch (err: any) {
set({ error: err.message, isLoading: false });
toast.error(err.message || 'Failed to fetch water logs');
}
},
addWaterLog: async (log: Omit<WaterLog, 'id' | 'createdAt'>) => {
set({ isLoading: true, error: null });
try {
const response = await fetch('/api/water-intake', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(log),
});
if (!response.ok) throw new Error('Failed to add water log');
const newLog = await response.json();
set({
waterLogs: [...get().waterLogs, newLog],
isLoading: false,
});
toast.success('Water intake logged');
} catch (err: any) {
set({ error: err.message, isLoading: false });
toast.error(err.message || 'Failed to add water log');
}
},
deleteWaterLog: async (id: string) => {
set({ isLoading: true, error: null });
try {
const response = await fetch(`/api/water-intake?id=${id}`, {
method: 'DELETE',
});
if (!response.ok) throw new Error('Failed to delete water log');
set({
waterLogs: get().waterLogs.filter((log) => log.id !== id),
isLoading: false,
});
toast.success('Water log deleted');
} catch (err: any) {
set({ error: err.message, isLoading: false });
toast.error(err.message || 'Failed to delete water log');
}
},
}));
Key Conventions
- Always wrap in
set({ isLoading: true, error: null }) - Use
await fetch()with proper method and headers - Check
!response.okto handle HTTP errors - Parse response with
await response.json() - Use
get()to access current state in actions - Update state with
set()includingisLoading: false - Call
toast.success()on success - Call
toast.error()on failure - Include error message in toast:
err.message || 'Default message' - For arrays: use spread operator to create new array
- State updates are immutable (don't mutate existing state)
Steps
- Ask user for action name, API endpoint, method, and types
- Open
src/lib/store/healthStore.ts - Add action type to HealthState interface
- Add initial state if needed (empty array, null, etc.)
- Implement action function following the pattern above
- Include proper error handling and toast notifications
- Format with Prettier
Implementation Checklist
- Action added to HealthState interface
- Initial state added if new data field
- Async action with try-catch
- Loading state managed (set/unset)
- Error state managed
- API endpoint correct
- HTTP method correct
- Response validation (!response.ok)
- Toast notifications added
- State updates are immutable
- get() used to access current state