Apollo Elements Apollo Elements Guides API Blog Toggle darkmode

Cool Tricks: Validating Variables

Queries that have non-nullable variables (i.e. required variables) will still attempt to subscribe even if their required variables are not set.

For example, this query reads the "route param" /:pageId and exports it as the $pageId variable:

query Post($postId: ID!) {
  route @client {
    params {
      postId @export(as: "postId")
    }
  }

  post(postId: $postId) {
    id
    title
    image
    content
  }
}

Let's imagine a client-side router which calculates the /:pageId route param from the current page URL, and updates the routeVar reactive variable on every page navigation.

import { ApolloClient, HttpLink, InMemoryCache, makeVar } from '@apollo/client/core';

// router implementation left to reader's imagination
import { router, makeRoute } from 'foo-uter';

export const routeVar = makeVar(makeRoute(window.location))

router.addEventListener('navigate', location =>
  routeVar(makeRoute(location)));

const client = new ApolloClient({
  link: new HttpLink({ uri: '/graphql' }),
  cache: new InMemoryCache({
    typePolicies: {
      Query: {
        fields: {
          route() {
            return routeVar();
          }
        }
      }
    }
  })
});

If a component subscribes to this query automatically, as is the default behaviour, the graphql server may respond with an error:

Variable ”$postId“ of required type ”ID!“ was not provided.

For queries like this one, where the variables are dynamic, (e.g. they're based on the current page URL), you have three good options to prevent these kinds of errors:

  1. Create a variable-validating link
  2. Override the shouldSubscribe method on your components to determine whether they should subscribe
  3. Opt in to the old behaviour with ValidateVariablesMixin

To prevent any operation from fetching without required variables, use hasAllVariables from @apollo-elements/lib to create an ApolloLink which checks every outgoing operation for required variables.

import { ApolloClient, ApolloLink, HttpLink, InMemoryCache } from '@apollo/client/core';

import { hasAllVariables } from '@apollo-elements/lib/has-all-variables';

export const client = new ApolloClient({
  cache: new InMemoryCache(),
  link: ApolloLink.from([
    new ApolloLink((operation, forward) =>
      hasAllVariables(operation) && forward(operation)),
    new HttpLink({ uri: '/graphql' }),
  ])
});

The <apollo-client> component from @apollo-elements/components/apollo-client and the createApolloClient({ validateVariables: true }) helper from @apollo-elements/lib/create-apollo-client both incorporate this link.

This option is great when you want to 'set it and forget it', and it works for any operation, not solely for queries, but it's heavy-handed. For more fine-grained control you can program each individual query component to defer querying.

Option 2: Override the shouldSubscribe Method

With this approach, you can control on a per-component basis when to subscribe.

import { ApolloQueryMixin } from '@apollo-elements/mixins/apollo-query-mixin';
import PostQuery from './Post.query.graphql';
import { routeVar } from '../variables';

class BlogPost extends ApolloQueryMixin(HTMLElement)<Data, Variables> {
  query = PostQuery;

  shouldSubscribe() {
    return !!(routeVar().params?.postId)
  }
}

customElements.define('blog-post', BlogPost);
import { ApolloQuery, customElement } from '@apollo-elements/lit-apollo';
import PostQuery from './Post.query.graphql';
import { routeVar } from '../variables';

@customElement('blog-post')
class BlogPost extends ApolloQuery<Data, Variables> {
  query = PostQuery;

  shouldSubscribe() {
    return !!(routeVar().params?.postId)
  }
}
import { ApolloQuery, customElement } from '@apollo-elements/fast';
import PostQuery from './Post.query.graphql';
import { routeVar } from '../variables';

@customElement({ name: 'blog-post' })
class BlogPost extends ApolloQuery<Data, Variables> {
  query = PostQuery;

  shouldSubscribe() {
    return !!(routeVar().params?.postId)
  }
}
import { useQuery, component } from '@apollo-elements/haunted';
import PostQuery from './Post.query.graphql';
import { routeVar } from '../variables';

function BlogPost() {
  const { data } = useQuery(PostQuery, {
    shouldSubscribe() {
      return !!(routeVar().params?.postId)
    },
  });
}

customElements.define('blog-post', component(BlogPost));
import { define, query, client } from '@apollo-elements/hybrids';
import PostQuery from './Post.query.graphql';
import { routeVar } from '../variables';

function shouldSubscribe() {
  return !!(routeVar().params?.postId)
}

define('blog-post', {
  client: client(window.__APOLLO_CLIENT__),
  query: query(PostQuery),
  shouldSubscribe: { get(host) { return shouldSubscribe(host); } }
});

Option 3: Restore the Old Behaviour with ValidateVariablesMixin

The old variable-validating behaviour is still available, but you have to opt-in to get it. Import the ValidateVariablesMixin from @apollo-elements/mixins/validate-variables-mixin and apply it to your base class

import { ValidateVariablesMixin, ApolloQueryMixin } from '@apollo-elements/mixins';

import NonNullableQuery from './NonNullable.query.graphql';

class NonNullable extends
ValidateVariablesMixin(ApolloQueryMixin(HTMLElement))<Data, Variables> {
  query = NonNullableQuery;
}

customElements.define('non-nullable');
import { ValidateVariablesMixin } from '@apollo-elements/mixins';
import { ApolloQuery, customElement } from '@apollo-elements/lit-apollo';

import NonNullableQuery from './NonNullable.query.graphql';

@customElement('non-nullable')
class NonNullable extends
ValidateVariablesMixin(ApolloQuery)<Data, Variables> {
  query = NonNullableQuery;
}
import { ValidateVariablesMixin } from '@apollo-elements/mixins';
import { ApolloQuery, customElement } from '@apollo-elements/fast';

import NonNullableQuery from './NonNullable.query.graphql';

@customElement({ name: 'non-nullable' })
class NonNullable extends
ValidateVariablesMixin(ApolloQuery)<Data, Variables> {
  query = NonNullableQuery;
}
> There's no `ValidateVariablesMixin` for haunted, so use one of the other techniques.
> There's no `ValidateVariablesMixin` for hybrids, so use one of the other techniques.