Skip to content

Latest commit

 

History

History
194 lines (166 loc) · 6.81 KB

File metadata and controls

194 lines (166 loc) · 6.81 KB
Error in user YAML: (<unknown>): did not find expected alphabetic or numeric character while scanning an alias at line 2 column 8
---
description: Guidelines for Mutation Hooks
globs: **/hooks/query/**/*.ts, **/_hooks/query/**/*.ts
alwaysApply: false
---

Guidelines for Mutation Hooks

Purpose and Overview

Mutation hooks abstract data modification operations (create, update, delete) using TanStack Query's useMutation hook. They provide loading states, error handling, and optimistic updates. These hooks connect UI components to the data access layer for write operations, keeping components focused on presentation rather than data manipulation logic.

Structure and Organization

Mutation Hooks Module Structure

src/
├── hooks/                      # Shared hooks module
│   └── query/                  # Shared query hooks (includes mutations)
│       └── [entity]/
│           ├── use-create-[entity]-mutation.ts        # Create operation
│           ├── use-update-[entity]-mutation.ts        # Update operation
│           ├── use-delete-[entity]-mutation.ts        # Delete operation
│           └── use-[specific-action]-[entity]-mutation.ts  # Other specific mutations
└── features/
    └── [feature-name]/
        └── _hooks/             # Feature-specific hooks
            └── query/          # Feature-specific query hooks (includes mutations)
                └── [entity]/
                    ├── use-create-[entity]-mutation.ts
                    ├── use-update-[entity]-mutation.ts
                    └── use-delete-[entity]-mutation.ts

Naming Conventions

Files

  • use-create-[entity]-mutation.ts: For create operations
  • use-update-[entity]-mutation.ts: For update operations
  • use-delete-[entity]-mutation.ts: For delete operations
  • use-[specific-action]-[entity]-mutation.ts: For other specific actions (e.g., use-set-active-[entity]-mutation.ts)

Functions

  • useCreate[Entity]Mutation: For create operations
  • useUpdate[Entity]Mutation: For update operations
  • useDelete[Entity]Mutation: For delete operations
  • use[SpecificAction][Entity]Mutation: For other specific actions

Types

  • UseCreate[Entity]MutationArgs: Arguments for create mutation hook
  • UseUpdate[Entity]MutationArgs: Arguments for update mutation hook
  • UseDelete[Entity]MutationArgs: Arguments for delete mutation hook
  • Use[SpecificAction][Entity]MutationArgs: Arguments for specific action mutation hook

Implementation Guidelines

Mutation Hooks

  • Use TanStack Query's useMutation hook
  • Import mutation functions directly from @/data
  • Use TypeScript's MutationOptions type for args
  • Spread args at the beginning to allow overriding any option
  • Always get queryClient using useQueryClient()
  • Handle cache invalidation in onSuccess
  • Handle errors in onError with optional error alerting utility
  • Call the provided callback after internal logic

CRUD Mutation Examples

1. Create Mutation

// use-create-[entity]-mutation.ts
import { type MutationOptions, useMutation, useQueryClient } from '@tanstack/react-query';
import { post[Entity]s, type Post[Entity]sData, type Post[Entity]sResponse } from '@/data';
import { alertApiError } from '@/utils/api-errors';

export type UseCreate[Entity]MutationArgs = MutationOptions<
  Post[Entity]sResponse,
  Error,
  Post[Entity]sData
>;

export function useCreate[Entity]Mutation(args: UseCreate[Entity]MutationArgs = {}) {
  const queryClient = useQueryClient();

  return useMutation({
    ...args,
    mutationFn: post[Entity]s,
    onSuccess: async (data, variables, context) => {
      await queryClient.invalidateQueries({ queryKey: ['/[entity]s'] });
      args.onSuccess?.(data, variables, context);
    },
    onError: (error, variables, context) => {
      if (args?.onError) return args.onError(error, variables, context);
      alertApiError(error);
    },
  });
}

2. Update Mutation

// use-update-[entity]-mutation.ts
import { type MutationOptions, useMutation, useQueryClient } from '@tanstack/react-query';
import {
  put[Entity]sBy[Entity]Id,
  type Put[Entity]sBy[Entity]IdData,
  type Put[Entity]sBy[Entity]IdResponse,
} from '@/data';
import { alertApiError } from '@/utils/api-errors';

export type UseUpdate[Entity]MutationArgs = MutationOptions<
  Put[Entity]sBy[Entity]IdResponse,
  Error,
  Put[Entity]sBy[Entity]IdData
>;

export function useUpdate[Entity]Mutation(args: UseUpdate[Entity]MutationArgs = {}) {
  const queryClient = useQueryClient();

  return useMutation({
    ...args,
    mutationFn: put[Entity]sBy[Entity]Id,
    onSuccess: async (data, variables, context) => {
      await queryClient.invalidateQueries({ queryKey: ['/[entity]s'] });
      args.onSuccess?.(data, variables, context);
    },
    onError: (error, variables, context) => {
      if (args?.onError) return args.onError(error, variables, context);
      alertApiError(error);
    },
  });
}

3. Delete Mutation

// use-delete-[entity]-mutation.ts
import { type MutationOptions, useMutation, useQueryClient } from '@tanstack/react-query';
import {
  delete[Entity]sBy[Entity]Id,
  type Delete[Entity]sBy[Entity]IdData,
  type Delete[Entity]sBy[Entity]IdResponse,
} from '@/data';
import { alertApiError } from '@/utils/api-errors';

export type UseDelete[Entity]MutationArgs = MutationOptions<
  Delete[Entity]sBy[Entity]IdResponse,
  Error,
  Delete[Entity]sBy[Entity]IdData
>;

export function useDelete[Entity]Mutation(args: UseDelete[Entity]MutationArgs = {}) {
  const queryClient = useQueryClient();

  return useMutation({
    ...args,
    mutationFn: delete[Entity]sBy[Entity]Id,
    onSuccess: async (data, variables, context) => {
      await queryClient.invalidateQueries({ queryKey: ['/[entity]s'] });
      args.onSuccess?.(data, variables, context);
    },
    onError: (error, variables, context) => {
      if (args?.onError) return args.onError(error, variables, context);
      alertApiError(error);
    },
  });
}

Complete CRUD Mutation Folder Structure Example

src/hooks/query/[entity]/
├── use-create-[entity]-mutation.ts    # Create
├── use-update-[entity]-mutation.ts    # Update
└── use-delete-[entity]-mutation.ts    # Delete

Best Practices

Cache Invalidation

  • Implement appropriate cache invalidation in onSuccess
  • Consider which queries need to be invalidated after a mutation
  • Use queryClient.invalidateQueries for cache invalidation

Error Handling

  • Implement appropriate error handling strategies
  • Forward errors to the consuming components
  • Consider global error handling for common error scenarios

Side Effects

  • Keep side effects (like showing toast notifications) in the component using the mutation
  • Use the onSuccess and onError callbacks for side effects

Mutation States

  • Expose and use mutation states (isLoading, isError, isSuccess) in UI components
  • Handle different states appropriately in the UI