CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-tanstack--vue-query

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

Pending
Overview
Eval results
Files

mutations.mddocs/

Data Mutations

Composables for creating, updating, and deleting data with optimistic updates, automatic query invalidation, and reactive state management.

Capabilities

useMutation

Main composable for data mutations with automatic error handling and query invalidation.

/**
 * Main composable for data mutations with optimistic updates
 * @param options - Mutation configuration options with Vue reactivity support
 * @param queryClient - Optional query client instance
 * @returns Reactive mutation state and execution functions
 */
function useMutation<TData, TError, TVariables, TContext>(
  options: UseMutationOptions<TData, TError, TVariables, TContext>,
  queryClient?: QueryClient
): UseMutationReturnType<TData, TError, TVariables, TContext>;

interface UseMutationOptions<TData, TError, TVariables, TContext> {
  mutationFn?: MaybeRefOrGetter<
    (variables: TVariables) => Promise<TData> | TData
  >;
  mutationKey?: MaybeRefOrGetter<MutationKey>;
  onMutate?: MaybeRefOrGetter<
    (variables: TVariables) => Promise<TContext | void> | TContext | void
  >;
  onSuccess?: MaybeRefOrGetter<
    (data: TData, variables: TVariables, context: TContext | undefined) => Promise<unknown> | unknown
  >;
  onError?: MaybeRefOrGetter<
    (error: TError, variables: TVariables, context: TContext | undefined) => Promise<unknown> | unknown
  >;
  onSettled?: MaybeRefOrGetter<
    (data: TData | undefined, error: TError | null, variables: TVariables, context: TContext | undefined) => Promise<unknown> | unknown
  >;
  retry?: MaybeRefOrGetter<boolean | number | ((failureCount: number, error: TError) => boolean)>;
  retryDelay?: MaybeRefOrGetter<number | ((retryAttempt: number, error: TError) => number)>;
  throwOnError?: MaybeRefOrGetter<boolean | ((error: TError) => boolean)>;
  meta?: MaybeRefOrGetter<MutationMeta>;
  shallow?: boolean;
}

interface UseMutationReturnType<TData, TError, TVariables, TContext> {
  data: Ref<TData | undefined>;
  error: Ref<TError | null>;
  failureCount: Ref<number>;
  failureReason: Ref<TError | null>;
  isError: Ref<boolean>;
  isIdle: Ref<boolean>;
  isPending: Ref<boolean>;
  isPaused: Ref<boolean>;
  isSuccess: Ref<boolean>;
  mutate: (variables: TVariables, options?: MutateOptions<TData, TError, TVariables, TContext>) => void;
  mutateAsync: (variables: TVariables, options?: MutateOptions<TData, TError, TVariables, TContext>) => Promise<TData>;
  reset: () => void;
  status: Ref<MutationStatus>;
  submittedAt: Ref<number>;
  variables: Ref<TVariables | undefined>;
}

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

Usage Examples:

import { useMutation, useQueryClient } from '@tanstack/vue-query';

// Basic mutation
const { mutate, isPending, error } = useMutation({
  mutationFn: (newPost) => 
    fetch('/api/posts', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(newPost)
    }).then(res => res.json()),
  onSuccess: () => {
    console.log('Post created successfully!');
  },
  onError: (error) => {
    console.error('Failed to create post:', error);
  }
});

// Trigger mutation
const createPost = () => {
  mutate({
    title: 'New Post',
    content: 'This is the content'
  });
};

// Mutation with query invalidation
const queryClient = useQueryClient();
const { mutate: updateUser } = useMutation({
  mutationFn: ({ id, data }) =>
    fetch(`/api/users/${id}`, {
      method: 'PUT',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(data)
    }).then(res => res.json()),
  onSuccess: (data, variables) => {
    // Invalidate and refetch user queries
    queryClient.invalidateQueries({ queryKey: ['users'] });
    queryClient.invalidateQueries({ queryKey: ['user', variables.id] });
  }
});

// Optimistic updates
const { mutate: toggleTodo } = useMutation({
  mutationFn: ({ id, completed }) =>
    fetch(`/api/todos/${id}`, {
      method: 'PATCH',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ completed })
    }).then(res => res.json()),
  onMutate: async ({ id, completed }) => {
    // 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 === id ? { ...todo, completed } : todo
      )
    );
    
    // Return context for rollback
    return { previousTodos };
  },
  onError: (err, variables, context) => {
    // Rollback on error
    if (context?.previousTodos) {
      queryClient.setQueryData(['todos'], context.previousTodos);
    }
  },
  onSettled: () => {
    // Always refetch after error or success
    queryClient.invalidateQueries({ queryKey: ['todos'] });
  }
});

// Async mutation with error handling
const { mutateAsync: deletePost } = useMutation({
  mutationFn: (postId) =>
    fetch(`/api/posts/${postId}`, { method: 'DELETE' })
      .then(res => {
        if (!res.ok) throw new Error('Failed to delete');
        return res.json();
      })
});

// Using async mutation
const handleDelete = async (postId) => {
  try {
    await deletePost(postId);
    router.push('/posts');
  } catch (error) {
    alert('Failed to delete post');
  }
};

// Mutation with retry logic
const { mutate: uploadFile } = useMutation({
  mutationFn: (file) => {
    const formData = new FormData();
    formData.append('file', file);
    return fetch('/api/upload', {
      method: 'POST',
      body: formData
    }).then(res => res.json());
  },
  retry: 3,
  retryDelay: (attemptIndex) => Math.min(1000 * 2 ** attemptIndex, 30000),
  onError: (error, variables, context) => {
    console.error(`Upload failed after retries:`, error);
  }
});

// Multiple mutations with shared state
const { mutate: saveDraft, isPending: isSaving } = useMutation({
  mutationFn: (draft) => fetch('/api/drafts', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(draft)
  })
});

const { mutate: publishPost, isPending: isPublishing } = useMutation({
  mutationFn: (post) => fetch('/api/posts', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(post)
  })
});

const isBusy = computed(() => isSaving.value || isPublishing.value);

Types

// Mutation key type
type MutationKey = ReadonlyArray<unknown>;

// Mutation status type
type MutationStatus = 'idle' | 'pending' | 'success' | 'error';

// Mutation function context
interface MutationObserverBaseResult<TData, TError, TVariables, TContext> {
  context: TContext | undefined;
  data: TData | undefined;
  error: TError | null;
  failureCount: number;
  failureReason: TError | null;
  isPaused: boolean;
  status: MutationStatus;
  submittedAt: number;
  variables: TVariables | undefined;
}

// Mutation metadata
interface MutationMeta extends Record<string, unknown> {
  [key: string]: unknown;
}

// Base mutation options
interface MutationObserverOptions<TData = unknown, TError = DefaultError, TVariables = void, TContext = unknown> {
  mutationFn?: (variables: TVariables) => Promise<TData> | TData;
  mutationKey?: MutationKey;
  onMutate?: (variables: TVariables) => Promise<TContext | void> | TContext | void;
  onSuccess?: (data: TData, variables: TVariables, context: TContext | undefined) => Promise<unknown> | unknown;
  onError?: (error: TError, variables: TVariables, context: TContext | undefined) => Promise<unknown> | unknown;
  onSettled?: (data: TData | undefined, error: TError | null, variables: TVariables, context: TContext | undefined) => Promise<unknown> | unknown;
  retry?: boolean | number | ((failureCount: number, error: TError) => boolean);
  retryDelay?: number | ((retryAttempt: number, error: TError) => number);
  throwOnError?: boolean | ((error: TError) => boolean);
  meta?: MutationMeta;
}

// Vue-specific mutation options type
type UseMutationOptionsBase<TData, TError, TVariables, TContext> = 
  MutationObserverOptions<TData, TError, TVariables, TContext> & ShallowOption;

// Default error type
type DefaultError = Error;

Install with Tessl CLI

npx tessl i tessl/npm-tanstack--vue-query

docs

helpers.md

index.md

mutations.md

plugin-setup.md

queries.md

query-client.md

status-utilities.md

tile.json