vue.jsapollovue-composition-apivue-apollo

Adding Apollo Mutations to a composable in Vue


I am encapsulating some Apollo mutations in a Composable in Vue to allow the functionality to be reused in multiple components however for one instance, I need to know what the parameters of the mutation were so I can create the update function.

When I try to wrap the mutation in a function, I receive the following error

Error: Apollo client with id default not found. Use provideApolloClient() if you are outside of a component setup.

This has not come up with any of the other queries / mutations in the composable so it seems that useMutation is losing scope within a function

import gql from 'graphql-tag';

export function useComposable() {
  const TEAMS_QUERY = gql`
    query Teams {
      teams {
        nodes {
          id
          name
        }
      }
    }
  `;

  const leaveTeam = ({ teamId, userId }) => {
    const { mutate } = useMutation(
      DELETE_TEAM_MEMBERSHIP,
      () => ({
        update(cache, { data: { deleteTeamMembership } }) {
          // use the function params here to update the cache
          let data = cache.readQuery({ query: TEAMS_QUERY });
          data = {
            ...data.teams
            nodes: data.teams.nodes.filter(
              (team) => team.id !== teamId
            )
          };
          cache.writeQuery({ query: TEAMS_QUERY, data });
        }
      })
    );
    return mutate({ teamId, userId });
  };

  return {
    leaveTeam
  };
}

I have re-written my function to the following (as recommended by the error) which works as expected (using the apollo provider from setup)

export function useComposable() {
  const TEAMS_QUERY = gql`
    query Teams {
      teams {
        nodes {
          id
          name
        }
      }
    }
  `;

  const leaveTeam = ({ teamId, userId }) => {
    const { mutate } = provideApolloClient(apollo)(() => useMutation(
      DELETE_TEAM_MEMBERSHIP,
      () => ({
        update(cache, { data: { deleteTeamMembership } }) {
          // use the function params here to update the cache
          let data = cache.readQuery({ query: TEAMS_QUERY });
          data = {
            ...data.teams
            nodes: data.teams.nodes.filter(
              (team) => team.id !== teamId
            )
          };
          cache.writeQuery({ query: TEAMS_QUERY, data });
        }
      })
    ));
    return mutate({ teamId, userId });
  };

  return {
    leaveTeam
  };
}

I could also have done this (which works in this context) which also works as expected (note: not inside a function but directly inside the composable)

export function useComposable() {
  const TEAMS_QUERY = gql`
    query Teams {
      teams {
        nodes {
          id
          name
        }
      }
    }
  `;

  const { mutate: leaveTeam } = useMutation(
    DELETE_TEAM_MEMBERSHIP,
    () => ({
      update(cache, { data: { deleteTeamMembership } }) {
        // use the function params here to update the cache
        let data = cache.readQuery({ query: TEAMS_QUERY });
        data = {
          ...data.teams
          nodes: data.teams.nodes.filter(
            (team) => team.id !== teamId
          )
        };
        cache.writeQuery({ query: TEAMS_QUERY, data });
      }
    })
  );

  return {
    leaveTeam
  };
}

However I'd like to know why the scope function is considered outside of the Vue Apollo context and needs it to be provided if anyone has an explanation?

The composable may then used within my sfc like

<script setup>
import { useComposable } from './useComposable';

const { leaveTeam } = useComposable();

// mocked data
const user = { id: 0, name: 'My Name' };

const teams = [
  { id: 0, name: 'Team 1' },
  { id: 1, name: 'Team 2' },
  { id: 2, name: 'Team 3' }
];
</script>

<template>
  <section class="teams-list">
    <h1>Admin {{ user.name }}</h1>
    <ul>
      <li v-for="team in teams" :key="team.id">
        {{ team.name }}
        <button @click="leaveTeam({ teamId: team.id, userId: user.id })">
          Leave Team
        </button>
      </li>
    </ul>
  <section>
</template>

Thanks


Solution

  • When creating a composable to encapsulate apollo functionality (I've found this especially useful to have the mutations with update functions alongside the queries they affect to maintain state between them), it seems like the following rules are useful.

    useComposable.js

    export function useComposable() {
      const TEAMS_QUERY = gql`query Teams {
        teams {
          nodes {
            id
            name
          }
        }
      }`;
    
      const { mutate } = () => useMutation(
        DELETE_TEAM_MEMBERSHIP,
        () => ({
          update(cache, _, { variables: { teamId } }) {
            // use the function params here to update the cache
            let data = cache.readQuery({ query: TEAMS_QUERY });
            data = {
              ...data.teams
              nodes: data.teams.nodes.filter(
                (team) => team.id !== teamId
              )
            };
            cache.writeQuery({ query: TEAMS_QUERY, data });
          }
        })
      );
    
      return {
        mutate
      };
    }
    

    Component.vue

    const team = { id: 1 };
    const user = { id: 1 };
    
    const { mutate } = useComposable();
    
    mutate({
      teamId: team.id,
      userId: user.id
    });
    
    export function useComposable() {
      const TEAMS_QUERY = gql`
        query Teams {
          teams {
            nodes {
              id
              name
            }
          }
        }
      `;
    
      const { mutate } = () => useMutation(
        DELETE_TEAM_MEMBERSHIP,
        () => ({
          update(cache, _, { variables: { teamId } }) {
            // use the function params here to update the cache
            let data = cache.readQuery({ query: TEAMS_QUERY });
            data = {
              ...data.teams
              nodes: data.teams.nodes.filter(
                (team) => team.id !== teamId
              )
            };
            cache.writeQuery({ query: TEAMS_QUERY, data });
          }
        })
      );
    
      const leaveTeam = ({ teamId, userId }) => {
        // some other functionality
        return mutate({ teamId, userId });
      };
    
    
      return {
        leaveTeam
      };
    }
    
    import apollo from '@/plugins/apollo.js';
    
    export function useComposable() {
      const TEAMS_QUERY = gql`
        query Teams {
          teams {
            nodes {
              id
              name
            }
          }
        }
      `;
    
      const leaveTeam = ({ teamId, userId }) => {
        const { mutate } = provideApolloClient(apollo)(() => useMutation(
          gql`mutation LeaveTeam($teamId: Int!, $userId: Int!) {
            leaveTeam(input: { teamId: $teamId, userId: $userId }) {
              team {
                id
              }
            }
          }`,
          () => ({
            update(cache, { data: { deleteTeamMembership } }) {
              // use the function params here to update the cache
              let data = cache.readQuery({ query: TEAMS_QUERY });
              data = {
                ...data.teams
                nodes: data.teams.nodes.filter(
                  (team) => team.id !== teamId
                )
              };
              cache.writeQuery({ query: TEAMS_QUERY, data });
            }
          })
        ));
        return mutate({ teamId, userId });
      };
    
    
      return {
        leaveTeam
      };
    }