Execute GraphQL mutations with state management and optimistic updates.
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>
);
}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>;
}