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:

As you can see, a couple things have gone wrong here:
update() is called twice.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!
@Akryum Here's the issue I was having that I mentioned in #185
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.
This still doesn't explain why the store cache is not updating. What should I do to fix this?
Actually, both calls are with fake data. Because trackId is an empty string. If I remove the optimisticResponse, Vue Apollo calls the mutation twice (I can see this in DevTools) and I wind up with two copies of the data in the database.
@Akryum I found the problem.
This now works as expected:
update: (store, { data: { createTrack } }) => {
const data = store.readQuery({
query: listTracksQuery,
// variables: {
// limit: this.pageSize,
// },
});
data.listTracks.items.push(createTrack);
store.writeQuery({ query: listTracksQuery, data });
},
And the query:
apollo: {
listTracks: {
query: listTracksQuery,
// variables() {
// return {
// limit: this.pageSize,
// };
// },
},
},
When I add the query variables back in, adding new items doesn't update the page at all (but the DB is updated).
Is this an Apollo bug? Pinging @stubailo just in case, since I realize Vue Apollo is mostly a wrapper.
you should make sure that you are getting back the data that you are expecting to update the page...
do you see any warnings?
No warnings, the data being returned is as expected.
@Akryum I have a reproduction repo. As you can see, when you add new items, you have to refresh the page to see them. However, if you do not pass any variables to the query (comment that code out), it updates immediately upon hitting enter.
@ffxsam Repo cannot be found error. repost it!
It’s a private repo and I gave access to Guillaume. I suppose I can make it public though. It does have an API key in it, but it’s not linked to any real data or anything.
I’ll change the access settings in a bit.
@Lahori-Jawan Done
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?
Ahhh, that was it! Thank you!
This is just a dummy reproduction.. but in my real app, I need to pass a limit variable since I'm paginating through results. If I leave off the variable when querying, there could be thousands of items returned. The real code:
const data = store.readQuery({
query: listTracksQuery,
variables: {
limit: this.pageSize,
order: this.order === 'ascending' ? 'asc' : 'desc',
sortBy: this.sortBy,
},
});
There's one more issue with this repro. If you add more than one item, you'll see an error in the console about a repeated key "??" - the optimistic UI is using this as a temporary id, and it's not getting updated properly from the actual server response. You can see this if you open Vue DevTools and look at the array of items. If you add several items, you'll see they all have "??" as the id.
Any ideas why?
Since optimistic response's main purpose is to update the UI in time independently from server and it needs all data that is required to update UI, you will have to feed it uuid from the client and then send this uuid to server to be stored instead of server to generate and send it back.
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.
let say the time client sends response to server is t0 and server sends back uuid at t1. Optimistic UI should update it at t1 but what about t0? It gets error at t0 and when response arrives at t1, Optimistic UI is in error state so it doesn't update the UI.
I don’t see any errors.
I'm going to close this and open a new issue. This is a new problem. Thanks for your help in discovering the caching problem!
I don't know about your scenario but simply I did this:
optimisticResponse: {
__typename: 'Mutation',
createThing: {
__typename: 'Thing',
uuid: ((Math.random()*7)+(Math.random()*7+8)+(Math.random()*7*5)),
name: this.newThing,
},
}
but it doesn't answer your question that server response is not being updated. May be you should raise this issue on apollo because server response is not being updated as you can see below

Good point.. this is probably an Apollo bug. I'll open an issue in that repo. Thanks!
Hi @ffxsam Where I can find the issue you have opened?. I have the same problem, my update is called twice. Im still struggling with it.
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,
},
});
}
Am I the only one that finds this extremely verbose and a lot of boilerplate code for something as simple as submitting a form?
@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.
It wasn't a criticism of the code shown. It's a question whether Apollo or
Vue Apollo could be improved to reduce boilerplate code.
On Sat, 31 Aug 2019, 14:23 Guillaume Chau, notifications@github.com wrote:
@Stefano1990 https://github.com/Stefano1990 I would like to see you
code that handle the result of your submit and also is optimistic. My guess
is it won't be much shorter.—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
https://github.com/Akryum/vue-apollo/issues/210?email_source=notifications&email_token=AABW3EIPI5EKQ3VVNR4ZZXLQHJPEJA5CNFSM4EQEOK5KYY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGOD5TLWZA#issuecomment-526826340,
or mute the thread
https://github.com/notifications/unsubscribe-auth/AABW3EJB66NZ7NITJTLRN5TQHJPEJANCNFSM4EQEOK5A
.
fetchPolicy: 'cache-and-network'
const cache = new InMemoryCache({ resultCaching: false });
@ThanhPhat1080 You save my day! Thanks a lot!!!
Most helpful comment
Thanks @ffxsam. I installed the repo and after running
npm startan input is shown on page. Then I fill it with data andcacheis being updated (after removing the params, as you suggested) but with params it works just fine.Since you are querying in
conditioni.e. variable, you have towriteinconditiontoo(don't ask me why). I copy/pasted varialbe fromreadQuerytowriteQuerylike this:but why you put variable in reading when you are going to push new object to main array nonetheless?