Debouncing GraphQL Apollo in Vue

Brian ScramlinBrian Scramlin
3 min read

Scenario

You have an input that must make a real-time query to the database. This is often an autocomplete, but in my case it is checking to see if a subdomain is unique.

Input Component

I am using PrimeVue, so that is where the components are coming from.

            <div class="flex flex-col gap-2">
              <label for="subdomain">Organization Subdomain</label>
              <InputText
                id="subdomain"
                v-model="subdomain"
                placeholder="i.e. grace-church-bc"
                :class="{ 'p-invalid': errors.subdomain }"
                fluid
              />
              <Message v-if="errors.subdomain" severity="error" variant="simple" size="small">{{
                errors.subdomain
              }}</Message>
              <Message
                v-if="subdomainMessage.message && !subdomainLoading"
                :severity="subdomainMessage.severity"
                variant="simple"
                size="small"
              >
                {{ subdomainMessage.message }}
              </Message>
            </div>

Nice.

GraphQL File Structure

When I work with GraphQL, Apollo, and Vue Apollo , I find separation extremely important. GraphQL is flexible in its ability to fetch just what you need, but it requires a lot of boiler plate. I find the following setup very good for the frontend portion—

src
|_graphql
    |_operations
        |_index.ts // barrel file
        |_subdomain.ts // one of many operation files
    |_composables
        |_index.ts // barrel file
        |_useCheckSubdomainAvailability.ts // one of many composables
    |_types
        |_index.ts // generated by codegen
|_App.vue
...

Operations

import { gql } from "graphql-tag";

export const CHECK_SUBDOMAIN_AVAILABILITY = gql`
  query CheckSubdomainAvailability($subdomain: String!) {
    checkSubdomainAvailability(subdomain: $subdomain) {
      available
      subdomain
    }
  }
`;

This is a simple GraphQL client query that uses the graphql-tag library to parse the GQL syntax. It takes a string and then calls our resolver checkSubdomainAvailability with the string as its argument. We are asking that the resolver give back available and subdomain. We don’t really need subdomain, but I don’t feel like changing the existing code at the moment.

Composables

import { useQuery } from "@vue/apollo-composable";
import { CHECK_SUBDOMAIN_AVAILABILITY } from "../operations/subdomain";
import { computed, type Ref } from "vue";
import { CheckSubdomainAvailabilityQuery } from "../types";

export function useCheckSubdomainAvailability(
  subdomain: Ref<string | undefined, string | undefined>,
) {
  const { result, loading, error, refetch } = useQuery<CheckSubdomainAvailabilityQuery>(
    CHECK_SUBDOMAIN_AVAILABILITY,
    computed(() => ({ subdomain: subdomain.value ?? "" })),
    {
      fetchPolicy: "no-cache",
      debounce: 500,
    },
  );

  const isAvailable = computed(() => result.value?.checkSubdomainAvailability?.available);

  return {
    loading,
    error,
    isAvailable,
    refetch,
  };
}

This is where the magic really happens.

💡
Notice that we are passing a Ref and not a String. This is vital as Vue Apollo’s useQuery() composable will refetch automatically when a ref is updated!

We then break out the usual variables from the useQuery composable. Notice we add the generated type using a TypeScript Generic, which allows us to have type safety wherever we use this composable throughout the application.

Next, notice that we are able to pass an options object to the useQuery composable. We pass fetchPolicy: "no-cache" just in case another user creates the same subdomain within the cache duration. This keeps the data fresh. Secondly, we pass debounce: 500. This means the composable will only make the query 500ms after the data changes and also cancel any previous requests it had started.

The Impact

This is crucial as we would otherwise overload our server with requests on every keystroke.

not-debounced.mov [video-to-gif output image]

But, with debounce, things work great.

debounced.mov [video-to-gif output image]

I love that debounce is built into the Vue Apollo composable so we do not need to create a separate function and utilize a third party library like VueUse or Lodash to accomplish this task.

0
Subscribe to my newsletter

Read articles from Brian Scramlin directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Brian Scramlin
Brian Scramlin

I am a full-stack developer from Battle Creek, MI. My focus is on enterprise TypeScript applications. I have worked with startups and legacy companies as a senior developer and currently serve the team at GradeCam.