Vue-apollo: The returned data are not reactive anymore

Created on 31 May 2017  路  12Comments  路  Source: vuejs/vue-apollo

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?

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.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)
      }
    }
  }

All 12 comments

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

Was this page helpful?
0 / 5 - 0 ratings

Related issues

mathe42 picture mathe42  路  4Comments

alx13 picture alx13  路  4Comments

beeplin picture beeplin  路  4Comments

dsbert picture dsbert  路  4Comments

jsrkstr picture jsrkstr  路  3Comments