or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

client-access.mdindex.mdmutation-operations.mdquery-operations.mdsubscription-operations.mdtesting-utilities.md
tile.json

mutation-operations.mddocs/

Mutation Operations

Execute GraphQL mutations with state management and optimistic updates.

Capabilities

useMutation Hook

Execute GraphQL mutations and manage mutation state, with support for optimistic updates and error handling.

/**
 * Execute GraphQL mutations and manage mutation state
 * @param mutation - GraphQL mutation document
 * @param options - Configuration options for the mutation
 * @returns Tuple with mutation function and mutation result
 */
function useMutation<TData = any, TVariables = OperationVariables, TContext = DefaultContext, TCache = ApolloCache<any>>(
  mutation: DocumentNode,
  options?: MutationHookOptions<TData, TVariables, TContext, TCache>
): MutationTuple<TData, TVariables, TContext, TCache>;

type MutationTuple<TData, TVariables, TContext, TCache> = [
  MutationFunction<TData, TVariables, TContext, TCache>,
  MutationResult<TData>
];

type MutationFunction<TData, TVariables, TContext, TCache> = (
  options?: MutationFunctionOptions<TData, TVariables, TContext, TCache>
) => Promise<FetchResult<TData>>;

interface MutationHookOptions<TData, TVariables, TContext, TCache> {
  /** Variables to pass to the mutation */
  variables?: TVariables;
  /** Optimistic response data */
  optimisticResponse?: TData | ((vars: TVariables) => TData);
  /** Update function to modify cache after mutation */
  update?: MutationUpdaterFunction<TData, TVariables, TContext, TCache>;
  /** Queries to refetch after mutation */
  refetchQueries?: Array<string | PureQueryOptions> | RefetchQueriesFunction;
  /** Whether to await refetch queries */
  awaitRefetchQueries?: boolean;
  /** Error policy for handling GraphQL errors */
  errorPolicy?: ErrorPolicy;
  /** Context passed to Apollo Link */
  context?: TContext;
  /** Mutation complete callback */
  onCompleted?: (data: TData) => void;
  /** Mutation error callback */
  onError?: (error: ApolloError) => void;
  /** Ignore results (fire-and-forget) */
  ignoreResults?: boolean;
  /** Client instance to use */
  client?: ApolloClient<TCache>;
}

interface MutationResult<TData> {
  /** Mutation data */
  data?: TData;
  /** Loading state */
  loading: boolean;
  /** Error state */
  error?: ApolloError;
  /** Whether mutation has been called */
  called: boolean;
  /** Apollo Client instance */
  client: ApolloClient<any>;
  /** Reset mutation state */
  reset(): void;
}

interface MutationFunctionOptions<TData, TVariables, TContext, TCache> {
  /** Variables to pass to the mutation */
  variables?: TVariables;
  /** Optimistic response data */
  optimisticResponse?: TData | ((vars: TVariables) => TData);
  /** Update function to modify cache after mutation */
  update?: MutationUpdaterFunction<TData, TVariables, TContext, TCache>;
  /** Queries to refetch after mutation */
  refetchQueries?: Array<string | PureQueryOptions> | RefetchQueriesFunction;
  /** Whether to await refetch queries */
  awaitRefetchQueries?: boolean;
  /** Error policy for handling GraphQL errors */
  errorPolicy?: ErrorPolicy;
  /** Context passed to Apollo Link */
  context?: TContext;
  /** Update queries in cache */
  updateQueries?: MutationQueryReducersMap<TData>;
}

Usage Examples:

import { useMutation, gql } from "@apollo/react-hooks";

const CREATE_USER = gql`
  mutation CreateUser($input: CreateUserInput!) {
    createUser(input: $input) {
      id
      name
      email
    }
  }
`;

function CreateUserForm() {
  const [createUser, { loading, error, data }] = useMutation(CREATE_USER, {
    // Optimistic response for instant UI updates
    optimisticResponse: (variables) => ({
      createUser: {
        __typename: "User",
        id: "temp-id",
        name: variables.input.name,
        email: variables.input.email,
      },
    }),
    // Update cache after successful mutation
    update: (cache, { data }) => {
      if (data?.createUser) {
        cache.modify({
          fields: {
            users(existingUsers = []) {
              const newUserRef = cache.writeFragment({
                data: data.createUser,
                fragment: gql`
                  fragment NewUser on User {
                    id
                    name
                    email
                  }
                `,
              });
              return [...existingUsers, newUserRef];
            },
          },
        });
      }
    },
    onCompleted: (data) => {
      console.log("User created:", data.createUser);
    },
    onError: (error) => {
      console.error("Error creating user:", error);
    },
  });

  const handleSubmit = async (formData: { name: string; email: string }) => {
    try {
      await createUser({
        variables: {
          input: formData,
        },
      });
    } catch (err) {
      // Error handled by onError callback
    }
  };

  return (
    <form onSubmit={(e) => {
      e.preventDefault();
      const formData = new FormData(e.currentTarget);
      handleSubmit({
        name: formData.get("name") as string,
        email: formData.get("email") as string,
      });
    }}>
      <input name="name" placeholder="Name" required />
      <input name="email" type="email" placeholder="Email" required />
      <button type="submit" disabled={loading}>
        {loading ? "Creating..." : "Create User"}
      </button>
      {error && <div>Error: {error.message}</div>}
      {data && <div>Created user: {data.createUser.name}</div>}
    </form>
  );
}

Advanced Usage with Cache Updates:

import { useMutation, gql, useQuery } from "@apollo/react-hooks";

const GET_USERS = gql`
  query GetUsers {
    users {
      id
      name
      email
    }
  }
`;

const DELETE_USER = gql`
  mutation DeleteUser($id: ID!) {
    deleteUser(id: $id) {
      id
    }
  }
`;

function UserList() {
  const { data: usersData } = useQuery(GET_USERS);
  const [deleteUser] = useMutation(DELETE_USER, {
    update: (cache, { data }) => {
      if (data?.deleteUser) {
        cache.modify({
          fields: {
            users(existingUsers, { readField }) {
              return existingUsers.filter(
                (userRef: any) => readField('id', userRef) !== data.deleteUser.id
              );
            },
          },
        });
      }
    },
    // Alternative approach using refetchQueries
    // refetchQueries: [{ query: GET_USERS }],
  });

  const handleDelete = (userId: string) => {
    deleteUser({ variables: { id: userId } });
  };

  return (
    <ul>
      {usersData?.users.map((user: any) => (
        <li key={user.id}>
          {user.name}
          <button onClick={() => handleDelete(user.id)}>Delete</button>
        </li>
      ))}
    </ul>
  );
}

Types

type MutationUpdaterFunction<TData, TVariables, TContext, TCache> = (
  cache: TCache,
  result: FetchResult<TData>,
  options: {
    context?: TContext;
    variables?: TVariables;
  }
) => void;

type RefetchQueriesFunction = (result: FetchResult) => InternalRefetchQueriesInclude;

interface FetchResult<TData = Record<string, any>, C = Record<string, any>, E = Record<string, any>> {
  data?: TData | null;
  errors?: ReadonlyArray<GraphQLError>;
  extensions?: E;
  context?: C;
}

interface MutationQueryReducersMap<T = { [key: string]: any }> {
  [queryName: string]: MutationQueryReducer<T>;
}

type MutationQueryReducer<T> = (
  previousResult: Record<string, any>,
  options: {
    mutationResult: FetchResult<T>;
    queryName: string | undefined;
    queryVariables: Record<string, any>;
  }
) => Record<string, any>;

interface PureQueryOptions {
  query: DocumentNode;
  variables?: Record<string, any>;
}