I’ve been using GraphQL since Vue-Apollo’s first release for every API development project and just like any other new working environment, Vue-Apollo has its own caveats. One of them is that you can’t simply update its own store without calling a mutation (in practice, actually a Query also updates the store but that not is the case for this subject).

Obviously, it’s completely logical from the developer aspect. If you’d like to keep your SSOT concept intact, this is the way.

But, and there’s always a but, there might be some occasions in which you may want to update your fetched GraphQL data (store in this context) on the client’s screen. In my case, here is the scenario:

On the client side, there’s a simple input and user enters some Id numbers into the database, On the same page, there’s a “Latest Id Numbers” list so user can see some results after submitting data, When page loads for the first time, last 10 of Id numbers fetched with a GraphQL Query, When user submits a new Id number, a Mutation works and updates the Query with the new data, On the server side, a Laravel command works every minute to check if an Id number marked as “invalidated”, There’s a 3rd party validation server for this Id numbers and this command fires a Job to send Id number data to validate, In the meantime, to show what’s the situation about this validation process to user without refreshing it’s page or polling the GraphQL server every second I use Pusher service and that Job fires an Event to update the “Latest Id Numbers” list and the fun begins, While the user can still be inserting new Id numbers to the database, he can still see his list feeding with the data he enters and with the data Pusher updates it.

The problem with this concept is, you may update the Vue data (derived from GraphQL query) and your page will still be totally fine. On the number 8 above, Pusher sends an update through its channel, and informs about updated data:

mounted () {

this.channel = this.$pusher.subscribe(`private-User.Entries.${this.user.id}`) this.channel.bind('pusher:subscription_succeeded', (e) => { console.warn('Subscribed to Entries channel.') }) this.channel.bind('App\\Events\\EntryStatus', (data) => { this.onUpdateEntry(data) })

}

In this code you may see whenever EntryStatus event is fired on Laravel side, it updates the Pusher and Pusher updates my Vue app then fires onUpdateEntry method with its updated data.

data () {

return {

pendingUpdates: []

}

}, methods: {

onUpdateEntry (entryUpdated) {

// First part const entryRendered = this.$apolloData.data.entriesPaginate.find(entry => entry.id === entryUpdated.id) if (entryRendered !== undefined) {

entryRendered.status = entryUpdated.status

} // Second part const index = this.pendingUpdates.findIndex(entry => entry.id === entryUpdated.id) if (index !== -1) {

this.$set(this.pendingUpdates, index, entryUpdated)

} else {

this.pendingUpdates.push(entryUpdated)

} }

This code updates the rendered GraphQL data and polls the updated entry data at the same time. Notice the entryRendered object. You may wonder why would anyone needs to poll the entryUpdated data when it’s possible to update the apolloData, right? The thing is, apolloData here, is not the actual Apollo store. It’s the dataset you may change it in your Query’s result method.

data () {

return {

pendingUpdates: [],

loading: false,

total: 0,

pagination: {

age: 1,

rowsPerPage: 10,

sortBy: 'id',

descending: true

},

entriesPaginate: []

}

}, apollo: {

entriesPaginate: () => ({

query: ENTRY_PAGINATE,

context: { uri: 'http://localhost/graphql' },

variables () {

return this.pagination

},

result ({ data: { entriesPaginate: { data, total } }, loading }) {

if (!loading) {

this.total = total

this.entriesPaginate = data.slice().map((key, i) => ({

...key,

__index: i

}))

}

},

watchLoading (isLoading) {

this.loading = isLoading

}

})

}

With Vue-Apollo, you may ignore the result method on your queries (and also the entriesPaginate array in data) and let the Apollo handle the assignment of query result to Vue data. But in my case, I needed the total data separately and there might be some other alterations on the data set. Therefore I use result method here. But use it or not, entriesPaginate array will be a slice of Apollo store’s original query result.

So it doesn’t matter if I update this.$apolloData.data.entriesPaginate or this.entriesPaginate in onUpdateEntry method. When user submits a new Id and triggers the Mutation, Apollo will trigger Query result method itself (defined or not), and this.entriesPaginate array will be updated with Apollo store’s original data. That means all the updates from onUpdateEntry method will be rolled back on the user’s screen. He/She will see the new Id entry and the old query’s data.

To overcome this problem, we would’ve been using an this.$apollo.store method, but there’s not such one. So I’ve came up with this work-around idea of polling the updated data. In onUpdateEntry method, you may notice there are two parts of code with comments, first and second. The second parts inserts the updatedEntry data into pendingUpdates array. I keep those records to update store first, when user submits a new Id into database via Mutation. Here it is:

data () {

return {

graphQLErrors: [],

form: {

idNumber: null

},

pendingUpdates: [], // Empty here for definition, actual data filled with multiple entryUpdated data.

}

}, methods: {

submit () {

this.loading = true this.$apollo.mutate({

mutation: ENTRY_CREATE,

variables: {

entry: {

id_number: this.form.idNumber

}

},

context: { uri: 'http://localhost/graphql' },

update: (store, { data: { entryCreated } }) => {

const query = {

query: ENTRY_PAGINATE,

variables: this.pagination

}, data = store.readQuery(query)



if (data !== undefined) {

// First part

if (this.pendingUpdates.length) {

this.pendingUpdates.forEach(entryUpdated => {

const index = data.entriesPaginate.data.findIndex(entry => entry.id === entryUpdated.id)

if (index !== -1) {

this.$set(data.entriesPaginate.data, index, entryUpdated)

}

}) this.pendingUpdates = [] // Reset

}



// Second part

if (data.entriesPaginate.data.findIndex(entry => entry.id === entryCreated.id) === -1) {

data.entriesPaginate.data.unshift(entryCreated)



store.writeQuery({

...query,

data: data

})

}

}

}

}).catch((e) => {

this.graphQLErrors = e

}).then(() => {

this.loading = false

})

}

}

You may notice here the update method of the Mutation is in two parts again. The first part, before updating the store with newly created data, iterates the pendingUpdates array and updates the Apollo store, then the second part inserts the entryCreated object at the top of the list and finally store.writeQuery finalizes the update.

The most important part of all above is, you have to publish the same GraphQL data to Pusher. I did not investigate if my Laravel GraphQL package capable of doing that, so I just mimic the way how GraphQL serves data. To be more clear, this is the example of GraphQL query results for entriesPaginate:

{

data: {

entriesPaginate: [

{id: 1, id_number: 12345678, created_at: SomeDate, validated_at: SomeDate, status: "queued", __typename: "Entry"},

{id: 2, id_number: 34567890, created_at: SomeDate, validated_at: SomeDate, status: "validated", __typename: "Entry"},

{id: 3, id_number: 45678901, created_at: SomeDate, validated_at: SomeDate, status: "validating", __typename: "Entry"},

...

],

total: 41,

__typename: "entriesPagination"

}

}

And this my broadcating Event’s broadcastWith method to send data to Pusher:

public function broadcastWith()

{

return [

'__typename' => 'Entry',

'id' => $this->entry->id,

'id_number' => $this->entry->id_number,

'status' => $this->entry->status,

'created_at' => $this->entry->created_at,

'validated_at' => $this->entry->validated_at

];

}

Keeping the data in the same format prevents Apollo store update errors.