apollo: Mutations calling update() twice; not updating store cache

I’m having a ton of issues with Vue Apollo, all surrounding mutations. I’ll try to list as many details as I possibly can, and it’s going to be lengthy, so bear with me.

First, the GraphQL schema stuff, so it’s clear what my data looks like. These are the relevant bits:

type Track {
  trackId: ID!
  name: String!
  key: String
  size: Int
  duration: Int
  createdAt: String!
  isTranscoding: Boolean
  didTranscodingFail: Boolean
  tags: [String]
}

type TrackList {
  items: [Track]
  totalCount: Int!
}

type Query {
  listTracks(userId: ID!, limit: Int, nextToken: String): TrackList
}

type Mutation {
  createTrack(
    userId: ID!
    name: String!
    createdAt: String!
    duration: Int
  ): Track
}

And here is the listTracks query followed by the createTrack mutation:

query ListTracks($userId: ID!, $limit: Int, $nextToken: String) {
  listTracks(userId: $userId, limit: $limit, nextToken: $nextToken) {
    items {
      trackId
      name
      duration
      createdAt
    }
    totalCount
  }
}

mutation CreateTrack(
  $userId: ID!
  $name: String!
  $createdAt: String!
  $duration: Int # this is temporary - we won't know till after transcoding
) {
  createTrack(
    userId: $userId
    name: $name
    createdAt: $createdAt
    duration: $duration
  ) {
    trackId
    name
    createdAt
    duration
  }
}

I’m using AWS AppSync, which, when using pagination queries, returns items (an array of the results) and nextToken which is used to paginate to the next set of results. Hence using TrackList as a result for listTracks.

Now, I have a Vue container component which handles all Apollo logic, and passes results as props to a child component. In the container component, I have the following relevant code:

export default {
  data() {
    return {
      listTracks: {},
      pageSize: 20,
    };
  },

  apollo: {
    listTracks: {
      query: listTracksQuery,
      variables() {
        return {
          limit: this.pageSize,
          userId: this.$store.state.auth.user.username,
        };
      },
    },
  },

The query works perfectly, no problems. I then pass listTracks.items into the child component. I’ve left out the mutation code for now, as it’s a bit of a mess, and I wanted to point out that queries work without issue.

Now onto the mutation, also in the container component:

this.$apollo
  .mutate({
    mutation: createTrackMutation,
    variables: {
      createdAt: new Date().toISOString(),
      duration,
      name,
      userId: this.$store.state.auth.user.username,
    },
    update: (store, { data: { createTrack } }) => {
      const data = store.readQuery({
        query: listTracksQuery,
        variables: {
          limit: this.pageSize,
          userId: this.$store.state.auth.user.username,
        },
      });
      data.listTracks.items.push(createTrack);
      console.log('after pushing new item:', data.listTracks.items);
      store.writeQuery({ query: listTracksQuery, data });
      console.log(
        'checking readQuery:',
        store.readQuery({
          query: listTracksQuery,
          variables: {
            limit: this.pageSize,
            userId: this.$store.state.auth.user.username,
          },
        })
      );
    },
    optimisticResponse: {
      __typename: 'Mutation',
      createTrack: {
        __typename: 'Track',
        trackId: '',
        ...newTrack,
      },
    },
  })
  .then(data => {
    console.log('done!', data);
  })
  .catch(err => {
    console.log('error', err);
  });

Here’s the result of mutating in the web app:

image

As you can see, a couple things have gone wrong here:

  1. update() is called twice.
  2. The Apollo cache was not updated properly (it still has only a single item in the items array after performing writeQuery().

I’m guessing this may have something to do with my actual array of results being in an items rather than at root level. I’m not sure.

Any help would be greatly appreciated!

About this issue

  • Original URL
  • State: closed
  • Created 6 years ago
  • Comments: 28 (2 by maintainers)

Most upvoted comments

Thanks @ffxsam. I installed the repo and after running npm start an input is shown on page. Then I fill it with data and cache is being updated (after removing the params, as you suggested) but with params it works just fine.

Since you are querying in condition i.e. variable, you have to write in condition too(don’t ask me why). I copy/pasted varialbe from readQuery to writeQuery like this:

store.writeQuery({ query: listThingsQuery, variables: {
  fakeVar: 'nothing',
 }, data });

but why you put variable in reading when you are going to push new object to main array nonetheless?

@Stefano1990 I would like to see you code that handles the result of your submit and also is optimistic. My guess is it won’t be much shorter.

uuid is generated on the server though. Optimistic UI is supposed to update twice: once with the temporary data provided in optimisticResponse, and then it should update again with real data from the server once it’s complete. It’s not doing that second step apparently.

It was even said above:

The update is getting called one for the optimistic UI result (fake), and the once for the actual result from the network. The optimistic UI is rollbacked at that time.

I’m getting two updates, both are with fake/temporary data.

Hi @ffxsam update function will be called twice, It’s right, optimisticResponse sends the request to update function twice, once locally & once when data from the server is returned. So just make some conditions to make your writeQuery write right data.

My prev issue when I post new data to list:

optimisticResponse: {
  __typename: TYPENAMES.MUTATION,
  createBook: {
    __typename: TYPENAMES.BOOK,
    ...bookUpdateData,
  },
},
update: (store, { data }) => {
  const bookCache = store.readQuery({
    query: GET_BOOKS,
    variables: {
      contributerId: loggedUserId,
    },
  });

  // Run twice so my bookCache.getContributerBooks push `data.createBook` twice
  bookCache.getContributerBooks.push(data.createBook);

  // Run twice so my cache has duplicate data and render duplicate items to UI
  store.writeQuery({
    query: QUERIES_BOOK.GET_CONTRIBUTER_BOOKS,
    variables: {
      contributerId: loggedUser.id,
    },
    data: {
      getContributerBooks: bookCache.getContributerBooks,
    },
  });
}

Resolved

optimisticResponse: {
  __typename: TYPENAMES.MUTATION,
  createBook: {
    __typename: TYPENAMES.BOOK,
    ...bookUpdateData,
  },
},
update: (store, { data }) => {
  const bookCache = store.readQuery({
    query: GET_BOOKS,
    variables: {
      contributerId: loggedUserId,
    },
  });

  // Run twice so I need to check have that createBook is existed or not
  const isExit = newBookCache.getContributerBooks.find(
    book => book.id === data.createBook.id,
  );

  // if not exist, I push to the list then bring this list to update to the cache
  if (!isExit) {
    bookCache.getContributerBooks.push(
      data.createBook,
    );
  }

  // My cache is updated correctly so my UI won't have duplicate item.
  store.writeQuery({
    query: QUERIES_BOOK.GET_CONTRIBUTER_BOOKS,
    variables: {
      contributerId: loggedUser.id,
    },
    data: {
      getContributerBooks: bookCache.getContributerBooks,
    },
  });
}

This still doesn’t explain why the store cache is not updating. What should I do to fix this?

The update is getting called one for the optimistic UI result (fake), and the once for the actual result from the network. The optimistic UI is rollbacked at that time.