Apollo Elements Guides API Blog

Mutations: Composing Mutations and Queries

Say you had this query for a user profile page:

query ProfileQuery($userId: ID!) {
  profile(userId: $userId) {
    id
    name
    picture
    birthday
  }
}

And you wanted to create a mutation component which displays the existing profile and updates aspects of it when the user edits them:

mutation UpdateProfileMutation($input: UpdateProfileInput) {
  id
  name
  picture
  birthday
}

To make it easier to write declarative mutations, you can import the <apollo-mutation> element from @apollo-elements/components, like in this example, which combines a query and a mutation into a single component:


import '@apollo-elements/components/apollo-mutation';
import { ApolloQueryMixin } from '@apollo-elements/mixins/apollo-query-mixin';

import ProfileQuery from './Profile.query.graphql';
import UpdateProfileMutation from 'UpdateProfile.mutation.graphql';

import type {
  ProfileQueryData as Data,
  ProfileQueryVariables as Variables,
} from '../schema';

const template = document.createElement('template');
template.innerHTML = `
  <h2>Profile</h2>

  <dl>
    <dt>Name</dt>
    <dd></dd>

    <dt>Picture</dt>
    <dd><img role="presentation"/></dd>

    <dt>Birthday</dt>
    <dd></dd>
  </dl>

  <form hidden>
    <h3>Edit</h3>
    <apollo-mutation input-key="input">
      <label>Name
        <input slot="variable" data-variable="name">
      </label>

      <label>Picture (URL)
        <input slot="variable" data-variable="picture">
      </label>

      <label>Birthday
        <input slot="variable" data-variable="birthday" type="date"/>
      </label>

      <button slot="trigger">Save</button>
    </apollo-mutation>
  </form>
`;

export class ProfilePage extends
ApolloQueryMixin(HTMLElement)<Data, Variables> {
  query = ProfileQuery;

  #loading = false;
  get loading() { return this.#loading; }
  set loading(value: boolean) {
    this.#loading = value;
    this.render();
  }

  #data: Data = null;
  get data() { return this.#data; }
  set data(value: Data) {
    this.#data = value;
    this.render()
  }

  $(selector) { return this.shadowRoot.querySelector(selector); }

  constructor() {
    super();
    this.attachShadow({ mode: 'open' }).append(template.content.cloneNode(true));
    this.$('apollo-mutation').mutation = UpdateProfileMutation;
    this.render();
  }

  render() {
    this.$('dl').hidden = this.loading || !this.data;
    this.$('dd:nth-of-type(0)').textContent = this.data?.name;
    if (this.data?.picture)
      this.$('dd img').src = this.data.picture;
    this.$('dd:nth-of-type(2)').textContent = this.data?.birthday;
    this.$('form').hidden = !this.data?.isMe;
  }
}

customElements.define('profile-page', ProfilePage);

import '@apollo-elements/components/apollo-mutation';
import { ApolloQuery, customElement, html } from '@apollo-elements/lit-apollo';
import { ifDefined } from 'lit-html/directives/if-defined';

import ProfileQuery from './Profile.query.graphql';
import UpdateProfileMutation from 'UpdateProfile.mutation.graphql';

import type {
  ProfileQueryData as Data,
  ProfileQueryVariables as Variables,
} from '../schema';

@customElement('profile-page')
export class ProfilePage extends ApolloQuery<Data, Variables> {
  query = ProfileQuery;

  render() {
    return html`
      <h2>Profile</h2>

      <dl ?hidden="${this.loading || !this.data}">
        <dt>Name</dt>
        <dd>${this.data?.name}</dd>

        <dt>Picture</dt>
        <dd><img src="${ifDefined(this.data?.picture)}"/></dd>

        <dt>Birthday</dt>
        <dd>${this.data?.birthday}</dd>
      </dl>

      <form ?hidden="${!this.data?.isMe}">
        <h3>Edit</h3>
        <apollo-mutation .mutation="${UpdateProfileMutation}" input-key="input">
          <label>Name
            <input slot="variable" data-variable="name">
          </label>

          <label>Picture (URL)
            <input slot="variable" data-variable="picture">
          </label>

          <label>Birthday
            <input slot="variable" data-variable="birthday" type="date"/>
          </label>

          <button slot="trigger">Save</button>
        </apollo-mutation>
      </form>
    `;
  }
}

import '@apollo-elements/components/apollo-mutation';
import { ApolloQuery, customElement, html } from '@apollo-elements/fast';

import ProfileQuery from './Profile.query.graphql';
import UpdateProfileMutation from 'UpdateProfile.mutation.graphql';

import type {
  ProfileQueryData as Data,
  ProfileQueryVariables as Variables,
} from '../schema';

const template = html<ProfilePage>`
  <h2>Profile</h2>

  <dl ?hidden="${x => x.loading || !x.data}">
    <dt>Name</dt>
    <dd>${x => x.data?.name}</dd>

    <dt>Picture</dt>
    <dd><img src="${x => x.data?.picture ?? null}"/></dd>

    <dt>Birthday</dt>
    <dd>${x => x.data?.birthday}</dd>
  </dl>

  <form ?hidden="${!x => x.data?.isMe}">
    <h3>Edit</h3>
    <apollo-mutation .mutation="${UpdateProfileMutation}" input-key="input">
      <label>Name
        <input slot="variable" data-variable="name">
      </label>

      <label>Picture (URL)
        <input slot="variable" data-variable="picture">
      </label>

      <label>Birthday
        <input slot="variable" data-variable="birthday" type="date"/>
      </label>

      <button slot="trigger">Save</button>
    </apollo-mutation>
  </form>
`;

@customElement({ name: 'profile-page', template })
export class ProfilePage extends ApolloQuery<Data, Variables> {
  query = ProfileQuery;
}

import '@apollo-elements/components/apollo-mutation';
import { useQuery, component, html } from '@apollo-elements/haunted';

import ProfileQuery from './Profile.query.graphql';
import UpdateProfileMutation from 'UpdateProfile.mutation.graphql';

import type {
  ProfileQueryData as Data,
  ProfileQueryVariables as Variables,
} from '../schema';

function ProfilePage() {
  const { data, loading } = useQuery(ProfileQuery);

  return html`
    <h2>Profile</h2>

    <dl ?hidden="${loading || !data}">
      <dt>Name</dt>
      <dd>${data?.name}</dd>

      <dt>Picture</dt>
      <dd><img src="${ifDefined(data?.picture)}"/></dd>

      <dt>Birthday</dt>
      <dd>${data?.birthday}</dd>
    </dl>

    <form ?hidden="${!data?.isMe}">
      <h3>Edit</h3>
      <apollo-mutation .mutation="${UpdateProfileMutation}" input-key="input">
        <label>Name
          <input slot="variable" data-variable="name">
        </label>

        <label>Picture (URL)
          <input slot="variable" data-variable="picture">
        </label>

        <label>Birthday
          <input slot="variable" data-variable="birthday" type="date"/>
        </label>

        <button slot="trigger">Save</button>
      </apollo-mutation>
    </form>
  `;

}

customElements.define('profile-page', component(ProfilePage));

import '@apollo-elements/components/apollo-mutation';
import { client, query, define, html } from '@apollo-elements/hybrids';

import ProfileQuery from './Profile.query.graphql';
import UpdateProfileMutation from 'UpdateProfile.mutation.graphql';

import type {
  ProfileQueryData as Data,
  ProfileQueryVariables as Variables,
} from '../schema';

const render = ({ data, loading }) => html`
  <h2>Profile</h2>

  <dl hidden="${loading || !data}">
    <dt>Name</dt>
    <dd>${data?.name}</dd>

    <dt>Picture</dt>
    <dd><img src="${data?.picture ?? null}"/></dd>

    <dt>Birthday</dt>
    <dd>${data?.birthday}</dd>
  </dl>

  <form hidden="${!data?.isMe}">
    <h3>Edit</h3>
    <apollo-mutation mutation="${UpdateProfileMutation}" input-key="input">
      <label>Name
        <input slot="variable" data-variable="name">
      </label>

      <label>Picture (URL)
        <input slot="variable" data-variable="picture">
      </label>

      <label>Birthday
        <input slot="variable" data-variable="birthday" type="date"/>
      </label>

      <button slot="trigger">Save</button>
    </apollo-mutation>
  </form>
`;

define('profile-page', {
  client: client(window.__APOLLO_CLIENT__),
  query: query(ProfileQuery),
  render,
});

Read more about the <apollo-mutation> component.