Search code examples
vue.jsvuejs2graphqlapollo-clientvue-apollo

Why is component data shared between two independent components with vue-apollo?


I am working on a new project with vue and vue-apollo.

I have one component showing the user name (UserShow.vue):

<template>
    <div v-if="!this.$apollo.queries.user.loading">
        Your name is: {{user.firstname}}
    </div>
</template>
<script>
    import gql from "graphql-tag";
    export default {
        apollo: {
            user: {
                query: gql`{
                   user (id: 1) {
                     id
                     firstname
                   }
                }`
            }
        }
    }
</script>

and one component for editing (UserEdit.vue):

<template>
    <div v-if="!this.$apollo.queries.user.loading">
        Edit your name: <input v-model="user.firstname" />
    </div>
</template>

<script>
    import gql from "graphql-tag";
    export default {
        apollo: {
            user: {
                query: gql`{
                   user (id: 1) {
                     id
                     firstname
                   }
                }`
            }
        }
    }
</script>

Both components are shown on the same page, absolutely no mutations involved. just these two simple components.That's all

As soon as I change the name in the input field, the username is updated in the View.

That feels strange to me and I don't like it.

Apollo is doing the query and populating the vue data() property of each component. But why is the data shared in some way? data in a vue component should be private to this component as long as I don't pass it to a subcomponent (like with v-model in the input field).

If I look into my Apollo Dev Console into the Cache I can't see the change in firstname. So how does it work?

If I don't want this behavior I have only one strange option:

I can add another attribute to the query like so:

<template>
    <div v-if="!this.$apollo.queries.user.loading">
        Edit your name:
        <input v-model="user.firstname" />
    </div>
</template>

<script>
    import userQuery from "./user";
    import gql from "graphql-tag";
    export default {
        apollo: {
            user: {
                query: gql`{
                   user (id: 1) {
                     id
                     firstname
                     age         // added to create a different query
                   }
                }`
            }
        }
    }
</script>

Adding "age" to the query changes the behavior. That's even stranger! How can I write reliable components with such a behavior? Or did I miss some crucial concept?

Source code can be found here: https://github.com/kicktipp/hello-apollo

Bug or feature?


Solution

  • I had a look at your code, and I'm pretty sure I know what's going on.

    If you read through this thread you'll see that data returned from queries was initially intended by Apollo to be immutable (you shouldn't use it as a v-model). In fact, it used to be frozen so attempts to use it as a data source resulted in broken reactivity.

    Then this happened. Now the query results are mutable and you are mutating Apollo's internal data.

    This explains why one update would immediately modify the display output of another component with an identical query.

    Try something like this instead:

    import userQuery from './user';
    
    export default {
      data() {
        return { user: {} };
      },
      apollo: {
        user: {
          query: userQuery,
          manual: true,
          result({ data }) {
            this.user = { ...data.user };
          },
        },
      },
    };
    

    Note the following:

    • user is initialized in data
    • manual: true is set so user isn't automatically set by Vue Apollo
    • In result we set this.user using a spread (or Object.assign) to avoid copying a reference. If the object has more than one level, consider using something like cloneDeep.

    You'll need to make similar modifications in all places where you intend to mutate the query results.