Agent skill

add-feature-hook

Creates TanStack Query hooks for API features with authentication. Use when connecting frontend to backend endpoints, creating data fetching hooks.

Stars 163
Forks 31

Install this agent skill to your Project

npx add-skill https://github.com/majiayu000/claude-skill-registry/tree/main/skills/data/add-feature-hook

SKILL.md

Add Feature Hook

Creates TanStack Query hooks for API features with automatic authentication.

Prerequisites

Generate latest API types (backend must be running):

bash
cd front && pnpm run generate:api

3-Layer API Pattern

NEVER call API directly in components!

Feature Hooks (use-items.ts)
  ↓ uses
Base Auth Hooks (use-api.ts: useAuthenticatedQuery/Mutation)
  ↓ uses
Generated SDK (lib/api/*.gen.ts from OpenAPI)

Workflow

1. Create Hook File

typescript
// lib/hooks/use-my-feature.ts
import { useSuspenseQuery, useMutation, useQueryClient } from '@tanstack/react-query';
import { useAuthenticatedQuery, useAuthenticatedMutation } from './use-api';
import {
  getMyFeatures,
  createMyFeature,
  updateMyFeature,
  deleteMyFeature,
} from '@/lib/api/sdk.gen';
import type { CreateMyFeatureRequest, MyFeatureResponse } from '@/lib/api/types.gen';

// Query key factory - prevents cache bugs
export const myFeatureKeys = {
  all: ['my-feature'] as const,
  list: (userId?: string) => [...myFeatureKeys.all, 'list', userId] as const,
  detail: (id: string) => [...myFeatureKeys.all, 'detail', id] as const,
};

2. Add Query Hook

typescript
// Regular query (for optional data)
export function useMyFeatures(userId?: string) {
  return useAuthenticatedQuery({
    queryKey: myFeatureKeys.list(userId),
    queryFn: async (token) => {
      const response = await getMyFeatures({
        headers: { Authorization: `Bearer ${token}` },
      });
      return response.data ?? [];
    },
    enabled: !!userId,
  });
}

// Suspense query (for required data - use in content components)
export function useMyFeaturesSuspense(userId: string) {
  return useAuthenticatedQuery({
    queryKey: myFeatureKeys.list(userId),
    queryFn: async (token) => {
      const response = await getMyFeatures({
        headers: { Authorization: `Bearer ${token}` },
      });
      return response.data ?? [];
    },
    suspense: true, // Enables Suspense mode
  });
}

3. Add Mutation Hooks

typescript
export function useCreateMyFeature() {
  const queryClient = useQueryClient();

  return useAuthenticatedMutation({
    mutationFn: async (data: CreateMyFeatureRequest, token) => {
      const response = await createMyFeature({
        headers: { Authorization: `Bearer ${token}` },
        body: data,
      });
      return response.data;
    },
    onSuccess: () => {
      queryClient.invalidateQueries({ queryKey: myFeatureKeys.all });
    },
    showSuccessToast: 'Created successfully!',
    showErrorToast: true, // Default error handling
  });
}

export function useDeleteMyFeature() {
  const queryClient = useQueryClient();

  return useAuthenticatedMutation({
    mutationFn: async (id: string, token) => {
      await deleteMyFeature({
        headers: { Authorization: `Bearer ${token}` },
        path: { id },
      });
    },
    onSuccess: () => {
      queryClient.invalidateQueries({ queryKey: myFeatureKeys.all });
    },
    showSuccessToast: 'Deleted successfully!',
  });
}

Query Key Factory

Always use query key factories to prevent cache bugs:

typescript
export const myFeatureKeys = {
  all: ['my-feature'] as const,
  list: (userId?: string) => [...myFeatureKeys.all, 'list', userId] as const,
  detail: (id: string) => [...myFeatureKeys.all, 'detail', id] as const,
  filtered: (filters: Filters) => [...myFeatureKeys.all, 'filtered', filters] as const,
};

Usage in Components

typescript
// Content component (with Suspense)
function MyFeatureContent({ userId }: { userId: string }) {
  const { data } = useMyFeaturesSuspense(userId);
  const createMutation = useCreateMyFeature();

  const handleCreate = async (data: CreateMyFeatureRequest) => {
    await createMutation.mutateAsync(data);
  };

  return (
    <div>
      {data.map((item) => <div key={item.id}>{item.name}</div>)}
      <button onClick={() => handleCreate({ name: 'New' })}>
        Create
      </button>
    </div>
  );
}

Toast Notifications

Automatic via useAuthenticatedMutation:

  • Success: Green toast with custom message
  • Error: Automatic error handling with red toast
  • 401: Auto-redirect to login

DO NOT add manual error handling in components.

Generated API Types

Types are auto-generated from backend OpenAPI:

typescript
// Import from generated types
import type { CreateMyFeatureRequest, MyFeatureResponse } from '@/lib/api/types.gen';

// Import API functions
import { getMyFeatures, createMyFeature } from '@/lib/api/sdk.gen';

DO NOT edit files in lib/api/ - regenerate instead.

Didn't find tool you were looking for?

Be as detailed as possible for better results