graphql – Vue-apollo subscriptions not working as expected

I am working on vue-apollo and was able to use mutations and queries, but am facing issue using subscriptions. The apollo-express server with subscriptions is working fine. When a new event is created on the backend I want the front end to update as well but this does not seem to be happening with the current code i have written.
Vue-cli version:5.0.4
Vue version: vue@3.2.30.

Any help would be appreciated.

My Code:

main.js

import { createApp, provide, h } from "vue";
import { ApolloClient, InMemoryCache } from "@apollo/client/core";
import { DefaultApolloClient } from "@vue/apollo-composable";
import App from "./App.vue";
import { splitLink } from "./uri-utility";
const defaultClient = new ApolloClient({
    // uri: "http://localhost:4000/graphql",
    link: splitLink,
    cache: new InMemoryCache(),
});

createApp({
    setup() {
        provide(DefaultApolloClient, defaultClient);
    },

    render: () => h(App),
}).mount("#app");

uri-utility.js

import { split, HttpLink } from "@apollo/client";
import { getMainDefinition } from "@apollo/client/utilities";
import { GraphQLWsLink } from "@apollo/client/link/subscriptions";
import { createClient } from "graphql-ws";

const httpLink = new HttpLink({
    uri: "http://localhost:4000/graphql",
});

const wsLink = new GraphQLWsLink(
    createClient({
        url: "ws://localhost:4000/graphql",
        options: {
            reconnect: true,
        },
    })
);

export const splitLink = split(
    ({ query }) => {
        const definition = getMainDefinition(query);
        return (
            definition.kind === "OperationDefinition" &&
            definition.operation === "subscription"
        );
    },
    wsLink,
    httpLink
);

App.vue

<template>
  <div>
    <div>
      <HelloWorld />
    </div>
    <!-- <div>
      <div v-if="loading">Loading..</div>
      <div v-else-if="error">Error: {{ error.message }}</div>
      <div v-else-if="events">
        <ul v-for="event in events" :key="event.id">
          <li>{{ event.title }}</li>
        </ul>
      </div>
    </div> -->

    <div>
      <input
        type="text"
        placeholder="find by title"
        v-model="searchItem"
      /><br />
      <button @click="getSearchEvent" class="btn btn-primary">
        Search event
      </button>
      <div v-if="searchRes">
        {{ searchRes.title }}<br />
        {{ searchRes.start }}<br />
        {{ searchRes.end }}
      </div>
    </div>
  </div>
</template>

<script>
import { useQuery, useResult } from "@vue/apollo-composable";
import gql from "graphql-tag";
import { ref } from "vue";
import HelloWorld from "./components/HelloWorld.vue";
export default {
  components: {
    HelloWorld,
  },
  setup() {
    const searchItem = ref("");
    const variables = ref({
      title: "skull2",
    });
    const { result, loading, error } = useQuery(
      gql`
        query ($title: String!) {
          getAllEvents {
            id
            title
            start
            end
          }
          getEvent(title: $title) {
            title
            start
            end
          }
        }
      `,
      variables,
      {
        notifyOnNetworkStatusChange: true,
      }
    );
    function getSearchEvent() {
      variables.value.title = searchItem.value;
      console.log(variables.value.title);
    }
    const events = useResult(result, null, (data) => data.getAllEvents);
    const searchRes = useResult(result, null, (data) => data.getEvent);
    return {
      searchItem,
      getSearchEvent,
      variables,
      events,
      searchRes,
      loading,
      error,
    };
  },
};
</script>
<style>
div {
  margin: 1em;
}
#app {
  font-family: Avenir, Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  color: #2c3e50;
  margin-top: 60px;
}
</style>

HelloWorld.vue


<script>
import {
  useMutation,
  useQuery,
  useResult,
  useSubscription,
} from "@vue/apollo-composable";
import gql from "graphql-tag";
import { reactive, ref } from "vue-demi";
import { watch } from "vue";

const GET_ALL_EVENTS = gql`
  query {
    getAllEvents {
      title
      start
      end
    }
  }
`;
const CREATE_EVENT = gql`
  mutation ($event: EventType!) {
    createEvent(event: $event) {
      title
      start
      end
    }
  }
`;

const EVENT_CREATED_SUB = gql`
  subscription {
    eventCreated {
      id
      title
      start
      end
    }
  }
`;
export default {
  setup() {
    const messages = ref([]);
    const { result, loading, error } = useQuery(GET_ALL_EVENTS);
    const events = useResult(result, []);
    const event = reactive({
      title: "",
      start: "",
      end: "",
    });
    //console.log(event.event);

    //mutation
    const {
      mutate: createEvent,
      loading: createEventLoading,
      error: createEventError,
    } = useMutation(CREATE_EVENT, () => ({
      variables: {
        event: event,
      },
      update: (cache, { data: { createEvent } }) => {
        const data = cache.readQuery({ query: GET_ALL_EVENTS });
        data.events.push(createEvent);
        cache.writeQuery({ query: GET_ALL_EVENTS, data });
      },
    }));

    //Subscription
    const { sub_result } = ref(useSubscription(EVENT_CREATED_SUB));
    console.log(sub_result);
    watch(
      sub_result,
      (data) => {
        messages.value.push(data.eventCreated);
        console.log("New message received:", data.eventCreated);
      },
      {
        lazy: true, // Don't immediately execute handler
      }
    );
    return {
      createEvent,
      createEventError,
      createEventLoading,
      event,
      events,
      messages,
      loading,
      error,
    };
  },
};
</script>

<template>
  <div class="container">
    <div>
      {{ messages }}
    </div>
    <div>
      <form @submit="createEvent()" class="card text-white bg-secondary mb-3">
        <div class="card-header">Create Event</div>
        <div class="card-body">
          <input
            type="text"
            v-model="event.title"
            placeholder="enter event name"
            class="form-control"
            required
          />
          <br />
          <input
            type="datetime-local"
            v-model="event.start"
            class="form-control"
            required
          />
          <br />
          <input
            type="datetime-local"
            v-model="event.end"
            class="form-control"
            required
          /><br />
          <button
            v-if="event.title && event.start && event.end"
            class="btn btn-primary"
          >
            create event
          </button>
        </div>
      </form>
      <div>
        <div v-if="loading">Loading..</div>
        <div v-else-if="error">Error: {{ error.message }}</div>
        <div v-else-if="events">
          <!-- <ul v-for="event in events" :key="event.id">
            <li>{{ event.title }}</li>
          </ul> -->
          <table class="tableFixHead">
            <thead>
              <th>Title</th>
              <th>Start Time</th>
              <th>End Time</th>
            </thead>
            <tbody>
              <tr v-for="event in events" :key="event.id">
                <td>{{ event.title }}</td>
                <td>{{ event.start }}</td>
                <td>{{ event.end }}</td>
              </tr>
            </tbody>
          </table>
        </div>
      </div>
    </div>
  </div>
</template>

<style scoped>
.tableFixHead {
  overflow-y: auto;
  height: 106px;
}
.tableFixHead thead th {
  position: sticky;
  top: 0;
}
table {
  border-collapse: collapse;
  width: 100%;
}
th,
td {
  padding: 8px 16px;
  border: 1px solid #ccc;
}
th {
  background: #eee;
}
input {
  margin: 1em;
}
</style>

Please let me know if you need any other detail. Thank you

Leave a Comment