When 'vue-apollo' gets a result from 'apollo-client' via 'SmartQuery' and sets the result to component properties, I found those properties are not reactive anymore. This problem arises when you use 'v-model' where a user wants to update previously received data.
To get a picture of the problem see the code from a fictional vue component:
<template>
<div>
<template v-if="loading > 0">
Loading ...
</template>
<template v-else>
<div>
<input v-model="Post.description"/>
</div>
<button v-on:click="update">Update</button>
</template>
</div>
</template>
<script>
import gql from 'graphql-tag'
const getPost = gql`
query Post($id: ID!) {
Post(id: $id) {
id
imageUrl
description
}
}`
const updatePost = gql`
mutation updatePost($description: String!, $imageUrl: String!, $id: ID!) {
updatePost(id: $id, description: $description, imageUrl: $imageUrl) {
id
imageUrl
description
}
}`
export default {
data: () => ({
Post: {},
loading: 0
}),
apollo: {
Post: {
query: getPost,
loadingKey: 'loading',
variables () {
return {
id: this.$route.params.id
}
}
}
},
methods: {
update () {
// The description property is not updated with the user input.
const { id, imageUrl, description } = this.Post
this.$apollo.mutate({
mutation: updatePost,
variables: {
id,
imageUrl,
description
}
}).then((result) => {
console.log(result)
}).catch((error) => {
console.log(error)
})
}
}
}
</script>
I want to know if this is a bug or expected behavior?
The data coming from Apollo is immutable. I recommend you to use a new data property if you want to modify it.
@tadejstanic apollo object is freezed you need to create other if you want use it with inputs and editing it i using Object.assign or lodash cloneDeep
Example:
apollo: {
Post: {
query: getPost,
loadingKey: 'loading',
variables () {
return {
id: this.$route.params.id
}
}
result ({ data }) {
this.data = Object.assign({}, data.Post)
}
}
}
At first, I was also looking for a solution in both proposed directions, which ends in new data property. But I thought we can do this at 'SmartQuery' level when vue-apollo assign the results to the component data properties.
So what do you think about this modification in 'smart-apollo.js':
nextResult (result) {
const { data, loading } = result
if (!loading) {
this.loadingDone()
}
if (typeof data === 'undefined') {
// No result
} else if (typeof this.options.update === 'function') {
this.vm[this.key] = this.options.update.call(this.vm, data)
} else if (data[this.key] === undefined) {
console.error(`Missing ${this.key} attribute on result`, data)
} else {
// THE MODIFICATION
this.vm[this.key] = Object.assign({}, this.vm[this.key], data[this.key])
// this.vm[this.key] = data[this.key] THIS WAS BEFORE
}
if (typeof this.options.result === 'function') {
this.options.result.call(this.vm, result)
}
}
This modification might be nice to include, I find myself copying objects lots. I typically use _.toPlainObject(...) from lodash. Requesting data from an API, then modifying that data is the basis of just about every web app out there. Is there an instance where you wouldn't want the ability to modify retrieved data, or where doing the conversion would break other behavior?
Personally, i prefer the approach as is. It aligns with flux in that there is a one way flow of data. Apollo gets data, vue-apollo maps it in and then pass those down as props to other components. By letting any one of those components update the object, you lose flux.
@Samuell1 your example seems to not work :( but when i change this.data to this.Post, it worked :). Thanks a lot :+1:
@Musbell yeah, because you need create data object in data function
data () {
return {
data: null
}
}
@Samuell1 noted :)
Thank you to the participants on this thread - I just got caught on this issue as well.
I'd strongly recommend adding something to the docs (maybe in "basic usage") that calls out the fact that data coming back from queries should not be mutated. Consider using the "warning" or "danger" style to really make sure everyone sees it.
@dweldon its in Apollo client docs.
@Samuell1 Thanks for pointing that out. Many users (myself included) may not need to use the apollo client directly, and therefore haven't done a careful read of its documentation before coming to vue-apollo. While I agree this may be a case of RTFM, it seems like this is an opportunity to prevent new users from getting confused.
@dweldon but vue-apollo is using apollo client directly, because its just integration but i know what you mean, maybe there should be link with explanation in docs somewhere.
Looks like it will be soon resolved and not needed? https://github.com/apollographql/apollo-client/pull/3883
Most helpful comment
@tadejstanic apollo object is freezed you need to create other if you want use it with inputs and editing it i using
Object.assignor lodashcloneDeepExample: