Mutation Hook

Purpose

The Mutation Hook provides a specialized React hook designed to handle remote data mutations such as POST, PUT, DELETE, and PATCH requests in applications using the SWR data fetching strategy. It addresses the need for controlled, manual mutation triggers with robust state tracking, concurrency control, and integration with the SWR cache system. This hook enables developers to perform mutations that can optimistically update UI state, gracefully handle errors with rollback capabilities, and synchronize mutation results with the global cache.

Unlike the parent topic's broader focus on mutation mechanisms and types, this subtopic implements the concrete hook logic that developers use to trigger and manage mutations with fine-grained control over mutation lifecycle and state.

Functionality

At its core, the Mutation Hook exposes a mutation API that includes:

Key Workflow

  1. Initialization: The hook stores references to the mutation key, fetcher function, and configuration options. It initializes internal state tracking mutation data, error, and loading status.

  2. Trigger Mutation: When trigger is called with an argument, it:

    • Serializes the mutation key.

    • Validates presence of key and fetcher.

    • Merges default and user-provided options.

    • Records a timestamp to track the current mutation session.

    • Sets isMutating state to true.

    • Calls SWR's mutate with the fetcher result to update cache and await the mutation outcome.

    • Upon success, updates data and clears errors, invoking success callbacks.

    • Upon failure, updates error, clears loading, invokes error callbacks, and optionally throws.

    • Ignores results from mutations that started before the latest timestamp to avoid race conditions.

  3. Reset Mutation State: Calling reset clears all mutation state and updates the timestamp to discard ongoing mutations.

Illustrative Code Snippet

const trigger = useCallback(
  async (arg: any, opts?: SWRMutationConfiguration<Data, Error>) => {
    const [serializedKey, resolvedKey] = serialize(keyRef.current)
    if (!fetcherRef.current || !serializedKey) {
      throw new Error('Missing fetcher or key.')
    }

    const options = mergeObjects(
      { populateCache: false, throwOnError: true },
      configRef.current,
      opts
    )

    const mutationStartedAt = getTimestamp()
    ditchMutationsUntilRef.current = mutationStartedAt
    setState({ isMutating: true })

    try {
      const data = await mutate<Data>(
        serializedKey,
        fetcherRef.current(resolvedKey, { arg }),
        { ...options, throwOnError: true }
      )

      if (ditchMutationsUntilRef.current <= mutationStartedAt) {
        setState({ data, isMutating: false, error: undefined })
        options.onSuccess?.(data, serializedKey, options)
      }
      return data
    } catch (error) {
      if (ditchMutationsUntilRef.current <= mutationStartedAt) {
        setState({ error: error as Error, isMutating: false })
        options.onError?.(error as Error, serializedKey, options)
        if (options.throwOnError) throw error
      }
    }
  },
  []
)

Integration

The Mutation Hook integrates tightly with the core SWR library and complements other mutation-related subtopics such as state management and type definitions by providing the practical hook interface for remote mutations.

By encapsulating mutation triggering and state tracking, this hook enables applications to perform remote mutations with reliable concurrency control and cache synchronization, seamlessly fitting into the larger SWR ecosystem.

Diagram

sequenceDiagram
  participant Component
  participant MutationHook
  participant SWRCache
  participant Fetcher

  Component->>MutationHook: call trigger(arg)
  MutationHook->>MutationHook: serialize key, validate fetcher/key
  MutationHook->>MutationHook: set isMutating = true, record timestamp
  MutationHook->>SWRCache: mutate(key, fetcher(resolvedKey, arg))
  SWRCache->>Fetcher: execute fetcher function
  Fetcher-->>SWRCache: return data or error
  SWRCache-->>MutationHook: mutation result
  alt success
    MutationHook->>MutationHook: update data, isMutating=false, clear error
    MutationHook->>Component: update state with data
  else error
    MutationHook->>MutationHook: update error, isMutating=false
    MutationHook->>Component: update state with error
  end

This sequence diagram illustrates the interaction flow when a mutation is triggered: the component initiates the mutation, the hook serializes and validates inputs, uses SWR's cache mutation mechanism to perform the fetcher call, and updates state based on success or failure. Timestamp checks ensure that only the latest mutation affects the state.