All skills
asyrafhussin avatar

/state-management

@10bb16e

React Query and Zustand patterns for state management. Use when implementing data fetching, caching, mutations, or client-side state. Triggers on tasks involving useQuery, useMutation, Zustand stores, caching, or state management.

Use this Skill: https://skilld.dev/gh/asyrafhussin/agent-skills/state-management

This session only. Nothing lands on disk.

rulesrq-mutation-callbacks.md

≈1.4k tokens on demand. Your agent reads this file only when SKILL.md points to it.

Mutation Callbacks

Impact: HIGH

Mutation callbacks (onSuccess, onError, onSettled, onMutate) provide hooks into the mutation lifecycle for side effects, cache updates, and error handling.

Bad Example

// Anti-pattern: Side effects in mutationFn
const mutation = useMutation({
  mutationFn: async (data) => {
    const result = await createUser(data);
    toast.success('User created!'); // Side effect in mutationFn
    queryClient.invalidateQueries({ queryKey: ['users'] }); // Cache update here
    navigate('/users'); // Navigation here
    return result;
  },
});

// Anti-pattern: Duplicate callbacks in hook and mutate call
const mutation = useMutation({
  mutationFn: createUser,
  onSuccess: () => {
    queryClient.invalidateQueries({ queryKey: ['users'] });
  },
});

// Later in component
mutation.mutate(data, {
  onSuccess: () => {
    queryClient.invalidateQueries({ queryKey: ['users'] }); // Duplicate!
    navigate('/users');
  },
});

// Anti-pattern: Not handling errors
const mutation = useMutation({
  mutationFn: createUser,
  onSuccess: (data) => {
    navigate(`/users/${data.id}`);
  },
  // No onError - user has no idea if it failed
});

Good Example

// Separate concerns: hook handles cache, component handles UI
function useCreateUser() {
  const queryClient = useQueryClient();

  return useMutation({
    mutationFn: createUser,
    // Cache-related side effects in the hook
    onSuccess: (newUser) => {
      // Update cache
      queryClient.invalidateQueries({ queryKey: ['users'] });
      // Or optimistically add to cache
      queryClient.setQueryData<User[]>(['users'], (old) =>
        old ? [...old, newUser] : [newUser]
      );
    },
    onError: (error) => {
      // Log errors for monitoring
      console.error('Create user failed:', error);
    },
  });
}

// Component handles UI-specific side effects
function CreateUserPage() {
  const navigate = useNavigate();
  const createUser = useCreateUser();

  const handleSubmit = (data: CreateUserInput) => {
    createUser.mutate(data, {
      // UI-specific callbacks
      onSuccess: (newUser) => {
        toast.success(`Welcome, ${newUser.name}!`);
        navigate(`/users/${newUser.id}`);
      },
      onError: (error) => {
        toast.error(error.message);
      },
    });
  };

  return <UserForm onSubmit={handleSubmit} isLoading={createUser.isPending} />;
}

// All callbacks with proper typing
interface MutationContext {
  previousUsers: User[] | undefined;
}

const mutation = useMutation<User, ApiError, CreateUserInput, MutationContext>({
  mutationFn: createUser,
  onMutate: async (variables) => {
    // Called before mutationFn
    // Cancel outgoing refetches
    await queryClient.cancelQueries({ queryKey: ['users'] });

    // Snapshot current value
    const previousUsers = queryClient.getQueryData<User[]>(['users']);

    // Return context for rollback
    return { previousUsers };
  },
  onSuccess: (data, variables, context) => {
    // data: returned from mutationFn
    // variables: passed to mutate()
    // context: returned from onMutate
    console.log(`Created user ${data.name} with email ${variables.email}`);
  },
  onError: (error, variables, context) => {
    // Rollback on error
    if (context?.previousUsers) {
      queryClient.setQueryData(['users'], context.previousUsers);
    }
    toast.error(`Failed to create user: ${error.message}`);
  },
  onSettled: (data, error, variables, context) => {
    // Always runs after success or error
    // Good place for cleanup
    queryClient.invalidateQueries({ queryKey: ['users'] });
  },
});

// Using mutateAsync with callbacks
async function handleSequentialMutations() {
  try {
    const user = await createUserMutation.mutateAsync(userData, {
      onSuccess: () => {
        // This runs before the promise resolves
        console.log('User created, creating profile...');
      },
    });

    await createProfileMutation.mutateAsync({ userId: user.id });
  } catch (error) {
    // Catches errors from either mutation
  }
}

// Conditional callbacks based on response
const mutation = useMutation({
  mutationFn: updateUser,
  onSuccess: (updatedUser, variables) => {
    if (updatedUser.requiresVerification) {
      navigate('/verify-email');
    } else if (variables.role !== updatedUser.role) {
      // Role change requires re-login
      logout();
    } else {
      queryClient.invalidateQueries({ queryKey: ['user', updatedUser.id] });
    }
  },
});

Why

  1. Separation of Concerns: Hook-level callbacks handle cache; component-level callbacks handle UI.

  2. Callback Order: onMutate -> mutationFn -> onSuccess/onError -> onSettled. Understanding this enables proper optimistic updates.

  3. Context Passing: onMutate can return context used by other callbacks for rollback or additional logic.

  4. Callback Composition: Both hook and mutate() callbacks run; hook callbacks first, then mutate() callbacks.

  5. Error Recovery: onError with context enables rolling back optimistic updates on failure.

  6. Cleanup: onSettled runs regardless of outcome, perfect for cleanup and final cache invalidation.

Callback execution order:

mutate(variables) called
    ↓
onMutate(variables) → returns context
    ↓
mutationFn(variables) executes
    ↓
Success?
  Yes → onSuccess(data, variables, context)
  No  → onError(error, variables, context)
    ↓
onSettled(data, error, variables, context)

Note: Callbacks defined in both useMutation and mutate() both execute. Use hook-level for shared logic and mutate-level for component-specific logic.

Source: SKILL.md on GitHub

No alerts16d5 checks · Risk SAFE
  • Gen Agent Trust Hub16d

    This skill provides secure and comprehensive documentation for managing application state using React Query and Zustand. It includes critical security warnings against storing sensitive data in local storage and promotes best practices for data fetching, caching, and error handling. No malicious patterns or security vulnerabilities were detected.

  • Socket16d

    No alerts

  • Snyk16d

    Risk: LOW · No issues

  • Runlayer6mo

    32 files scanned · No issues

  • ZeroLeaks5mo

    Score: 93/100 · 2 sections analyzed

Signed by skilld at 10bb16e. This ties the file your Agent reads to that commit on GitHub. It does not review the instructions.

Last checked against GitHub last month.

Steadyupdated 7 months ago
metadata
{
  "author": "agent-skills",
  "version": "1.1.0"
}

README badge

README badge for asyrafhussin/agent-skills/state-management