Claude
Skills
Sign in
Back

relay-mutations-patterns

Included with Lifetime
$97 forever

Use when relay mutations with optimistic updates, connections, declarative mutations, and error handling.

General

What this skill does


# Relay Mutations Patterns

Master Relay mutations for building interactive applications with optimistic
updates, connection handling, and declarative data updates.

## Overview

Relay mutations provide a declarative way to update data with automatic cache
updates, optimistic responses, and rollback on error. Mutations integrate
seamlessly with Relay's normalized cache and connection protocol.

## Installation and Setup

### Mutation Configuration

```javascript
// mutations/CreatePostMutation.js
import { graphql, commitMutation } from 'react-relay';
import environment from '../RelayEnvironment';

const mutation = graphql`
  mutation CreatePostMutation($input: CreatePostInput!) {
    createPost(input: $input) {
      postEdge {
        __typename
        cursor
        node {
          id
          title
          body
          createdAt
          author {
            id
            name
          }
        }
      }
    }
  }
`;

export default function createPost(title, body) {
  return new Promise((resolve, reject) => {
    commitMutation(environment, {
      mutation,
      variables: {
        input: { title, body }
      },
      onCompleted: (response, errors) => {
        if (errors) {
          reject(errors);
        } else {
          resolve(response);
        }
      },
      onError: reject
    });
  });
}
```

## Core Patterns

### 1. Basic Mutation

```javascript
// CreatePost.jsx
import { graphql, useMutation } from 'react-relay';

const CreatePostMutation = graphql`
  mutation CreatePostMutation($input: CreatePostInput!) {
    createPost(input: $input) {
      post {
        id
        title
        body
        author {
          id
          name
        }
      }
    }
  }
`;

function CreatePost() {
  const [commit, isInFlight] = useMutation(CreatePostMutation);

  const handleSubmit = (title, body) => {
    commit({
      variables: {
        input: { title, body }
      },
      onCompleted: (response, errors) => {
        if (errors) {
          console.error('Errors:', errors);
        } else {
          console.log('Post created:', response.createPost.post);
        }
      },
      onError: (error) => {
        console.error('Network error:', error);
      }
    });
  };

  return (
    <form onSubmit={(e) => {
      e.preventDefault();
      handleSubmit(e.target.title.value, e.target.body.value);
    }}>
      <input name="title" placeholder="Title" disabled={isInFlight} />
      <textarea name="body" placeholder="Body" disabled={isInFlight} />
      <button type="submit" disabled={isInFlight}>
        {isInFlight ? 'Creating...' : 'Create Post'}
      </button>
    </form>
  );
}
```

### 2. Optimistic Updates

```javascript
// LikeButton.jsx
import { graphql, useMutation } from 'react-relay';

const LikePostMutation = graphql`
  mutation LikePostMutation($input: LikePostInput!) {
    likePost(input: $input) {
      post {
        id
        likesCount
        viewerHasLiked
      }
    }
  }
`;

function LikeButton({ post }) {
  const [commit, isInFlight] = useMutation(LikePostMutation);

  const handleLike = () => {
    commit({
      variables: {
        input: { postId: post.id }
      },

      // Optimistic response
      optimisticResponse: {
        likePost: {
          post: {
            id: post.id,
            likesCount: post.likesCount + 1,
            viewerHasLiked: true
          }
        }
      },

      // Optimistic updater
      optimisticUpdater: (store) => {
        const postRecord = store.get(post.id);
        if (postRecord) {
          postRecord.setValue(post.likesCount + 1, 'likesCount');
          postRecord.setValue(true, 'viewerHasLiked');
        }
      }
    });
  };

  return (
    <button onClick={handleLike} disabled={isInFlight}>
      {post.viewerHasLiked ? 'Unlike' : 'Like'} ({post.likesCount})
    </button>
  );
}
```

### 3. Connection Updates

```javascript
// CreateComment.jsx
const CreateCommentMutation = graphql`
  mutation CreateCommentMutation(
    $input: CreateCommentInput!
    $connections: [ID!]!
  ) {
    createComment(input: $input) {
      commentEdge @appendEdge(connections: $connections) {
        cursor
        node {
          id
          body
          createdAt
          author {
            id
            name
            avatar
          }
        }
      }
    }
  }
`;

function CreateComment({ postId, connectionID }) {
  const [commit, isInFlight] = useMutation(CreateCommentMutation);

  const handleSubmit = (body) => {
    commit({
      variables: {
        input: { postId, body },
        connections: [connectionID]
      },

      // No manual updater needed, @appendEdge handles it

      optimisticResponse: {
        createComment: {
          commentEdge: {
            cursor: 'temp-cursor',
            node: {
              id: `temp-${Date.now()}`,
              body,
              createdAt: new Date().toISOString(),
              author: {
                id: currentUser.id,
                name: currentUser.name,
                avatar: currentUser.avatar
              }
            }
          }
        }
      }
    });
  };

  return (
    <form onSubmit={(e) => {
      e.preventDefault();
      handleSubmit(e.target.body.value);
      e.target.reset();
    }}>
      <textarea name="body" placeholder="Add a comment..." />
      <button type="submit" disabled={isInFlight}>Post</button>
    </form>
  );
}

// Usage with connection ID
function Post({ post }) {
  const data = useFragment(
    graphql`
      fragment Post_post on Post {
        id
        comments(first: 10)
        @connection(key: "Post_comments") {
          edges {
            node {
              id
              ...Comment_comment
            }
          }
        }
      }
    `,
    post
  );

  const connectionID = ConnectionHandler.getConnectionID(
    post.id,
    'Post_comments'
  );

  return (
    <div>
      <CommentsList comments={data.comments.edges} />
      <CreateComment postId={post.id} connectionID={connectionID} />
    </div>
  );
}
```

### 4. Manual Cache Updates

```javascript
// DeletePost.jsx
const DeletePostMutation = graphql`
  mutation DeletePostMutation($input: DeletePostInput!) {
    deletePost(input: $input) {
      deletedPostId
    }
  }
`;

function DeletePost({ postId, onDelete }) {
  const [commit] = useMutation(DeletePostMutation);

  const handleDelete = () => {
    commit({
      variables: {
        input: { id: postId }
      },

      updater: (store) => {
        // Remove from connection
        const root = store.getRoot();
        const connection = ConnectionHandler.getConnection(
          root,
          'PostsList_posts'
        );

        if (connection) {
          ConnectionHandler.deleteNode(connection, postId);
        }

        // Delete the record
        store.delete(postId);
      },

      optimisticUpdater: (store) => {
        const root = store.getRoot();
        const connection = ConnectionHandler.getConnection(
          root,
          'PostsList_posts'
        );

        if (connection) {
          ConnectionHandler.deleteNode(connection, postId);
        }
      },

      onCompleted: () => {
        onDelete?.();
      }
    });
  };

  return (
    <button onClick={handleDelete} className="delete-button">
      Delete
    </button>
  );
}
```

### 5. Complex Updater Functions

```javascript
// UpdatePost.jsx
const UpdatePostMutation = graphql`
  mutation UpdatePostMutation($input: UpdatePostInput!) {
    updatePost(input: $input) {
      post {
        id
        title
        body
        status
        updatedAt
      }
    }
  }
`;

function UpdatePost({ post }) {
  const [commit] = useMutation(UpdatePostMutation);

  const handleUpdate = (title, body, status) => {
    commit({
      variables: {
        input: {
          id: post.id,
          title,
          body,
          status
        }
      },

      updater: (store, data) => {
        const updatedPost = data.updatePost.post;
      

Related in General