CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-react-query

Hooks for managing, caching and syncing asynchronous and remote data in React

Pending
Overview
Eval results
Files

mutations.mddocs/

Mutations

Data mutation operations with optimistic updates, automatic error handling, and cache invalidation patterns. The useMutation hook handles side effects like creating, updating, or deleting data.

Capabilities

useMutation Hook

The main hook for performing data mutations with loading states and error handling.

/**
 * Create a mutation for performing side effects
 * @param options - Mutation configuration options
 * @returns Mutation result with mutate functions and states
 */
function useMutation<TData = unknown, TError = unknown, TVariables = void, TContext = unknown>(
  options: UseMutationOptions<TData, TError, TVariables, TContext>
): UseMutationResult<TData, TError, TVariables, TContext>;

/**
 * Create a mutation with separate mutationFn parameter
 * @param mutationFn - Function that performs the mutation
 * @param options - Additional mutation configuration
 * @returns Mutation result with mutate functions and states
 */
function useMutation<TData = unknown, TError = unknown, TVariables = void, TContext = unknown>(
  mutationFn: MutationFunction<TData, TVariables>,
  options?: Omit<UseMutationOptions<TData, TError, TVariables, TContext>, 'mutationFn'>
): UseMutationResult<TData, TError, TVariables, TContext>;

/**
 * Create a mutation with mutation key for tracking and deduplication
 * @param mutationKey - Unique identifier for the mutation
 * @param options - Mutation configuration
 * @returns Mutation result with mutate functions and states
 */
function useMutation<TData = unknown, TError = unknown, TVariables = void, TContext = unknown>(
  mutationKey: MutationKey,
  mutationFn: MutationFunction<TData, TVariables>,
  options?: Omit<UseMutationOptions<TData, TError, TVariables, TContext>, 'mutationKey' | 'mutationFn'>
): UseMutationResult<TData, TError, TVariables, TContext>;

Usage Examples:

import { useMutation, useQueryClient } from "react-query";

// Basic mutation
const createPost = useMutation({
  mutationFn: (newPost) => fetch('/api/posts', {
    method: 'POST',
    body: JSON.stringify(newPost)
  }).then(res => res.json()),
  onSuccess: () => {
    alert('Post created!');
  }
});

// Mutation with cache invalidation
const updateUser = useMutation({
  mutationFn: ({ id, data }) => fetch(`/api/users/${id}`, {
    method: 'PUT',
    body: JSON.stringify(data)
  }),
  onSuccess: (data, variables) => {
    queryClient.invalidateQueries({ queryKey: ['user', variables.id] });
  }
});

// Optimistic mutation
const toggleTodo = useMutation({
  mutationFn: ({ id, completed }) => 
    fetch(`/api/todos/${id}`, {
      method: 'PATCH',
      body: JSON.stringify({ completed })
    }),
  onMutate: async (variables) => {
    // Cancel outgoing refetches
    await queryClient.cancelQueries({ queryKey: ['todos'] });
    
    // Snapshot previous value
    const previousTodos = queryClient.getQueryData(['todos']);
    
    // Optimistically update
    queryClient.setQueryData(['todos'], (old) =>
      old.map(todo => 
        todo.id === variables.id 
          ? { ...todo, completed: variables.completed }
          : todo
      )
    );
    
    return { previousTodos };
  },
  onError: (err, variables, context) => {
    // Rollback on error
    queryClient.setQueryData(['todos'], context.previousTodos);
  },
  onSettled: () => {
    // Always refetch after error or success
    queryClient.invalidateQueries({ queryKey: ['todos'] });
  }
});

// Using the mutations
const handleCreatePost = () => {
  createPost.mutate({ title: 'New Post', content: 'Content' });
};

const handleUpdateAsync = async () => {
  try {
    const result = await updateUser.mutateAsync({ 
      id: 1, 
      data: { name: 'Updated Name' } 
    });
    console.log('Update successful:', result);
  } catch (error) {
    console.error('Update failed:', error);
  }
};

Mutation Result Interface

The return value from useMutation containing mutation functions and states.

interface UseMutationResult<TData = unknown, TError = unknown, TVariables = unknown, TContext = unknown> {
  /** Function to trigger the mutation (fire-and-forget) */
  mutate: UseMutateFunction<TData, TError, TVariables, TContext>;
  /** Async function to trigger mutation and return promise */
  mutateAsync: UseMutateAsyncFunction<TData, TError, TVariables, TContext>;
  /** Data returned from successful mutation */
  data: TData | undefined;
  /** Error object if mutation failed */
  error: TError | null;
  /** True if mutation is in error state */
  isError: boolean;
  /** True if mutation is currently running */
  isPending: boolean;
  /** True if mutation succeeded */
  isSuccess: boolean;
  /** True if mutation has been triggered at least once */
  isIdle: boolean;
  /** Current status of the mutation */
  status: 'idle' | 'pending' | 'error' | 'success';
  /** Variables passed to the last mutation call */
  variables: TVariables | undefined;
  /** Context returned from onMutate */
  context: TContext | undefined;
  /** Number of times mutation has failed */
  failureCount: number;
  /** Reason mutation is paused */
  failureReason: TError | null;
  /** True if mutation is paused */
  isPaused: boolean;
  /** Function to reset mutation state */
  reset: () => void;
}

type UseMutateFunction<TData = unknown, TError = unknown, TVariables = void, TContext = unknown> = (
  variables: TVariables,
  options?: MutateOptions<TData, TError, TVariables, TContext>
) => void;

type UseMutateAsyncFunction<TData = unknown, TError = unknown, TVariables = void, TContext = unknown> = (
  variables: TVariables,
  options?: MutateOptions<TData, TError, TVariables, TContext>
) => Promise<TData>;

Mutation Options Interface

Configuration options for useMutation hook.

interface UseMutationOptions<TData = unknown, TError = unknown, TVariables = void, TContext = unknown> {
  /** Unique identifier for the mutation */
  mutationKey?: MutationKey;
  /** Function that performs the mutation */
  mutationFn?: MutationFunction<TData, TVariables>;
  /** Retry configuration */
  retry?: boolean | number | ((failureCount: number, error: TError) => boolean);
  /** Delay between retries */
  retryDelay?: number | ((retryAttempt: number, error: TError) => number);
  /** Callback before mutation starts (for optimistic updates) */
  onMutate?: (variables: TVariables) => Promise<TContext> | TContext | void;
  /** Callback on successful mutation */
  onSuccess?: (data: TData, variables: TVariables, context: TContext | undefined) => Promise<unknown> | unknown;
  /** Callback on mutation error */
  onError?: (error: TError, variables: TVariables, context: TContext | undefined) => Promise<unknown> | unknown;
  /** Callback after mutation settles (success or error) */
  onSettled?: (data: TData | undefined, error: TError | null, variables: TVariables, context: TContext | undefined) => Promise<unknown> | unknown;
  /** React context for QueryClient */
  context?: React.Context<QueryClient | undefined>;
  /** Whether to throw errors to error boundaries */
  useErrorBoundary?: boolean | ((error: TError, variables: TVariables, context: TContext | undefined) => boolean);
  /** Additional metadata */
  meta?: MutationMeta;
  /** Network mode configuration */
  networkMode?: 'online' | 'always' | 'offlineFirst';
}

interface MutateOptions<TData = unknown, TError = unknown, TVariables = void, TContext = unknown> {
  /** Override onSuccess for this call */
  onSuccess?: (data: TData, variables: TVariables, context: TContext | undefined) => Promise<unknown> | unknown;
  /** Override onError for this call */
  onError?: (error: TError, variables: TVariables, context: TContext | undefined) => Promise<unknown> | unknown;
  /** Override onSettled for this call */
  onSettled?: (data: TData | undefined, error: TError | null, variables: TVariables, context: TContext | undefined) => Promise<unknown> | unknown;
}

Mutation Function Interface

The function that actually performs the mutation.

type MutationFunction<TData = unknown, TVariables = void> = (
  variables: TVariables
) => Promise<TData>;

type MutationKey = readonly unknown[];

type MutationMeta = Record<string, unknown>;

Advanced Usage Patterns

Cache Updates

Different strategies for updating cached data after mutations:

const queryClient = useQueryClient();

// 1. Invalidation (refetch)
const mutation = useMutation({
  mutationFn: updateUser,
  onSuccess: (data, variables) => {
    // Invalidate and refetch
    queryClient.invalidateQueries({ queryKey: ['user', variables.id] });
    queryClient.invalidateQueries({ queryKey: ['users'] });
  }
});

// 2. Direct cache update
const mutation = useMutation({
  mutationFn: updateUser,
  onSuccess: (updatedUser, variables) => {
    // Update specific user query
    queryClient.setQueryData(['user', variables.id], updatedUser);
    
    // Update users list
    queryClient.setQueryData(['users'], (oldUsers) =>
      oldUsers.map(user => 
        user.id === variables.id ? updatedUser : user
      )
    );
  }
});

// 3. Add to cache (for create operations)
const createMutation = useMutation({
  mutationFn: createUser,
  onSuccess: (newUser) => {
    // Add to users list
    queryClient.setQueryData(['users'], (oldUsers) => [
      ...oldUsers,
      newUser
    ]);
    
    // Set individual user query
    queryClient.setQueryData(['user', newUser.id], newUser);
  }
});

Global Mutation States

Monitor all mutations using useIsMutating:

import { useIsMutating } from "react-query";

function GlobalLoadingIndicator() {
  const isMutating = useIsMutating();
  
  if (isMutating) {
    return <div>Saving changes...</div>;
  }
  
  return null;
}

// Filter specific mutations
function UserUpdateIndicator({ userId }: { userId: string }) {
  const isUpdatingUser = useIsMutating({
    mutationKey: ['updateUser', userId]
  });
  
  return isUpdatingUser ? <div>Updating user...</div> : null;
}

Error Recovery

const mutation = useMutation({
  mutationFn: updateData,
  retry: (failureCount, error) => {
    // Don't retry client errors (4xx)
    if (error.status >= 400 && error.status < 500) {
      return false;
    }
    
    // Retry server errors up to 3 times
    return failureCount < 3;
  },
  retryDelay: (attemptIndex) => Math.min(1000 * 2 ** attemptIndex, 30000),
  onError: (error, variables, context) => {
    // Handle error
    toast.error(`Failed to update: ${error.message}`);
    
    // Log for analytics
    analytics.track('mutation_error', {
      mutation: 'updateData',
      error: error.message,
      variables
    });
  }
});

// Manual retry
if (mutation.isError) {
  return (
    <div>
      <p>Update failed: {mutation.error.message}</p>
      <button onClick={() => mutation.reset()}>
        Dismiss
      </button>
      <button onClick={() => mutation.mutate(lastVariables)}>
        Retry
      </button>
    </div>
  );
}

Sequential Mutations

Chain mutations together:

function useSequentialMutations() {
  const queryClient = useQueryClient();
  
  const createUser = useMutation({
    mutationFn: (userData) => api.createUser(userData)
  });
  
  const assignRole = useMutation({
    mutationFn: ({ userId, roleId }) => api.assignRole(userId, roleId)
  });
  
  const sendWelcomeEmail = useMutation({
    mutationFn: (userId) => api.sendWelcomeEmail(userId)
  });
  
  const createUserWithRole = async (userData, roleId) => {
    try {
      const user = await createUser.mutateAsync(userData);
      await assignRole.mutateAsync({ userId: user.id, roleId });
      await sendWelcomeEmail.mutateAsync(user.id);
      
      // Refresh users list
      queryClient.invalidateQueries({ queryKey: ['users'] });
      
      return user;
    } catch (error) {
      // Handle any step failure
      console.error('User creation process failed:', error);
      throw error;
    }
  };
  
  return {
    createUserWithRole,
    isLoading: createUser.isPending || assignRole.isPending || sendWelcomeEmail.isPending
  };
}

Install with Tessl CLI

npx tessl i tessl/npm-react-query

docs

context.md

error-handling.md

index.md

infinite-queries.md

mutations.md

parallel-queries.md

queries.md

ssr.md

status.md

tile.json