Vue-apollo: Data doesn't get updated while refreshing nor WriteQuery

Created on 7 May 2018  ·  4Comments  ·  Source: vuejs/vue-apollo

Hello,

First of all, I'm not sure I'm doing it correctly, but here is my statement.

This is a « pretty simple » use case where I want to register an user and get the state updated with his data.

<template>
  <div class="UserMenu">
    <div class="ButtonWrapper">
      <Button :color="primaryColor">{{ $t('header.becomeCoach') }}</Button>
    </div>
    <div class="ButtonWrapper" v-if="!user">
      <Button :color="primaryColor" @click="displayLogin">{{ $t('header.connect') }}</Button>
      <div style="display: flex; justifyContent: center">
        <LoginModal @onRegister="register" :register-error="registerErrorMsg" />
      </div>
    </div>
    <div v-else>
      <Button class="Connected-Button" @click="showMenu = !showMenu">
        <img src="https://placeimg.com/40/40/people" />
        <span>&nbsp;{{ user.firstname }}&nbsp;</span>
        <icon :name="showMenu ? 'chevron-up' : 'chevron-down'" />
      </Button>
      <div v-if="showMenu" class="Connected-Menu">
        <div>Email: {{ user.email }}</div>
        <a href="">Mon profil</a>
        <a href="" @click="logout">Se deconnecter</a>
      </div>
    </div>

  </div>
</template>

<script lang="ts">
import gql from "graphql-tag";
import Vue from "vue";
import LoginModal from "../../components/Home/LoginModal.vue";
import Button from "../Common/Button.vue";

import { colors } from "../../services/variables";

export default Vue.extend({
  apollo: {
    user: {
      fetchPolicy: "cache-and-network",
      query: gql`
        {
          user: me {
            _id
            email
            firstname
          }
        }`,
    },
  },
  components: { Button, LoginModal },
  data() {
    return {
      primaryColor: colors.primary,
      registerErrorMsg: null,
      showMenu: false,
      user: null,
    };
  },
  methods: {
    displayLogin() {
      this.$modal.show("login-modal");
    },
    logout(e: Event) {
      e.preventDefault();
      const request = new XMLHttpRequest();
      request.open("POST", "http://localhost:3001/logout");
      request.withCredentials = true;
      request.send();

      // TODO: CALL client.resetStore()
      this.user = null;
    },
    register(values) {
      const request = new XMLHttpRequest();
      request.open("POST", "http://localhost:3001/register");
      request.setRequestHeader("Content-Type", "application/json");
      request.withCredentials = true;

      request.addEventListener("load", (event) => {
        const { status, response } = request;
        if (status === 401) {
          this.registerErrorMsg = this.$t("home.accountModal.validation.emailAlreadyTaken");
          return;
        }
        this.registerErrorMsg = null;
        this.$modal.hide("login-modal");
        this.$apollo.queries.user.refetch();

        // TODO: this could be counter solution
        // this.user = JSON.parse(response).user;
      });

      request.send(JSON.stringify(values));
    },
  },
});
</script>

As you can see I'm using this.$apollo.queries.user.refetch(); in order to refetch the user metadata after setting the jwt as a cookie while returning the response from the XHR.

The request is correctly outgoing and return the correct result from Network tab, but randomly, I don't get my user data updated. If I refresh the tab, it update and work properly.

I also tried to use $forceUpdate() nothing changed. Also setTimeout to avoid race condition…

Am I doing wrong ? Am I thinking in a bad way ?

Thanks for help,
Best regards

Most helpful comment

@Akryum I updated my whole code in order to use mutations now, Just in case that was buggy, what I have now is the following :

<template>
  <div class="UserMenu">
    <div class="ButtonWrapper">
      <Button :color="primaryColor">{{ $t('header.becomeCoach') }}</Button>
    </div>
    <div class="ButtonWrapper" v-if="$apollo.queries.me.loading">
      <Button :color="primaryColor">{{ $t('commons.loading') }}</Button>
    </div>
    <template v-else>
      <div class="ButtonWrapper" v-if="!me">
      <Button :color="primaryColor" @click="displayLogin">{{ $t('header.connect') }}</Button>
      <div style="display: flex; justifyContent: center">
        <LoginModal 
          @onLogin="login" 
          :login-error="loginErrorMsg" />
      </div>
    </div>
    <div v-else>
      <Button class="Connected-Button" @click="showMenu = !showMenu">
        <img src="https://placeimg.com/40/40/people" />
        <span>&nbsp;{{ me.firstname }}&nbsp;</span>
        <icon :name="showMenu ? 'chevron-up' : 'chevron-down'" />
      </Button>
      <div v-if="showMenu" class="Connected-Menu">
        <strong>{{ me.email }}</strong>
        <a href="">Mon profil</a>
      </div>
    </div>
    </template>
  </div>
</template>

<script lang="ts">
import gql from "graphql-tag";
import Vue from "vue";
import LoginModal from "../../components/Home/LoginModal.vue";
import Button from "../Common/Button.vue";

import { DataProxy } from "apollo-cache";
import { colors } from "../../services/variables";

const meQuery = gql`
      {
        me {
          _id
          email
          firstname
        }
      }
    `;

export default Vue.extend({
  apollo: {
    me: {
      query: meQuery,
    },
  },
  components: { Button, LoginModal },
  data() {
    return {
      loginErrorMsg: null,
      me: false,
      primaryColor: colors.primary,
      registerErrorMsg: null,
      showMenu: false,
    };
  },
  methods: {
    displayLogin() {
      this.$modal.show("login-modal");
    },
    async login(values) {
      try {
        await this.$apollo.mutate({
          mutation: gql`mutation login($email: String!, $password: String!) {
            login(email: $email, password: $password) {
              _id
              email
              firstname
            }
          }`,
          update: (store: DataProxy, { data: { login: me } }) => {
            store.writeQuery({
              data: {
                me,
              },
              query: meQuery,
            });
          },
          variables: values,
        });

        this.loginErrorMsg = null;
        this.$modal.hide("login-modal");
      } catch (error) {
        this.loginErrorMsg = this.$t("home.accountModal.validation.badCredentials");
      }
    },
  },
});
</script>

My cache is getting correctly updated, I checked it using the dev tool, but my data isn't getting updated, like it was not reactive…

Any idea ?

Regards

All 4 comments

You can write the user data into the store with writeQuery: https://www.apollographql.com/docs/react/advanced/caching.html#writequery-and-writefragment

@Akryum I updated my whole code in order to use mutations now, Just in case that was buggy, what I have now is the following :

<template>
  <div class="UserMenu">
    <div class="ButtonWrapper">
      <Button :color="primaryColor">{{ $t('header.becomeCoach') }}</Button>
    </div>
    <div class="ButtonWrapper" v-if="$apollo.queries.me.loading">
      <Button :color="primaryColor">{{ $t('commons.loading') }}</Button>
    </div>
    <template v-else>
      <div class="ButtonWrapper" v-if="!me">
      <Button :color="primaryColor" @click="displayLogin">{{ $t('header.connect') }}</Button>
      <div style="display: flex; justifyContent: center">
        <LoginModal 
          @onLogin="login" 
          :login-error="loginErrorMsg" />
      </div>
    </div>
    <div v-else>
      <Button class="Connected-Button" @click="showMenu = !showMenu">
        <img src="https://placeimg.com/40/40/people" />
        <span>&nbsp;{{ me.firstname }}&nbsp;</span>
        <icon :name="showMenu ? 'chevron-up' : 'chevron-down'" />
      </Button>
      <div v-if="showMenu" class="Connected-Menu">
        <strong>{{ me.email }}</strong>
        <a href="">Mon profil</a>
      </div>
    </div>
    </template>
  </div>
</template>

<script lang="ts">
import gql from "graphql-tag";
import Vue from "vue";
import LoginModal from "../../components/Home/LoginModal.vue";
import Button from "../Common/Button.vue";

import { DataProxy } from "apollo-cache";
import { colors } from "../../services/variables";

const meQuery = gql`
      {
        me {
          _id
          email
          firstname
        }
      }
    `;

export default Vue.extend({
  apollo: {
    me: {
      query: meQuery,
    },
  },
  components: { Button, LoginModal },
  data() {
    return {
      loginErrorMsg: null,
      me: false,
      primaryColor: colors.primary,
      registerErrorMsg: null,
      showMenu: false,
    };
  },
  methods: {
    displayLogin() {
      this.$modal.show("login-modal");
    },
    async login(values) {
      try {
        await this.$apollo.mutate({
          mutation: gql`mutation login($email: String!, $password: String!) {
            login(email: $email, password: $password) {
              _id
              email
              firstname
            }
          }`,
          update: (store: DataProxy, { data: { login: me } }) => {
            store.writeQuery({
              data: {
                me,
              },
              query: meQuery,
            });
          },
          variables: values,
        });

        this.loginErrorMsg = null;
        this.$modal.hide("login-modal");
      } catch (error) {
        this.loginErrorMsg = this.$t("home.accountModal.validation.badCredentials");
      }
    },
  },
});
</script>

My cache is getting correctly updated, I checked it using the dev tool, but my data isn't getting updated, like it was not reactive…

Any idea ?

Regards

@Akryum Sorry for UP, but still get stuck with that, If you take time to answer, it'll be a pleasure to udpate documentation through PR for the subject…

Seems, I'm not alone as shown with this "+1"

Regards

@Akryum Still more Thumb on my post…

I figured out something new. Instead of triggering an error on not Authenticated user, I return an empty user with empty fields (I was using https://github.com/kkemple/graphql-auth before with that me resolver).


Before :

 me: withAuth((obj, values, context) => context.auth.user),

Now :

me: (obj, values, context) => context.auth.user || { _id: "", email: "", firstname: "" },

Now the request is correctly reactive. And my view is updated accordingly.

I think your lib have a Reactivity glitch when the graph response is an error. Any idea what can cause that ?

Thanks for the response. Hope this can help improve that soft.

Regards

Was this page helpful?
0 / 5 - 0 ratings

Related issues

agosto-chrisbartling picture agosto-chrisbartling  ·  4Comments

jsrkstr picture jsrkstr  ·  3Comments

beeplin picture beeplin  ·  4Comments

AruXc picture AruXc  ·  4Comments

alx13 picture alx13  ·  4Comments