John Au-Yeung I'm a web developer interested in JavaScript stuff.

What’s new in Apollo Client 3

3 min read 1087

What's New In Apollo Client 3

Released in mid-July, Apollo Client 3 offers a few new features, including package rearrange changes and more caching features. Let’s proceed to take a look at how to use the latest features from Apollo Client 3.

InMemoryCache APIs

The InMemoryCache API has expanded features. They include the eviction of objects and fields, garbage collection, types and fields configuration, and pagination helpers.

Let’s explore these changes by installing the @apollo/client package with its dependencies by running:

npm i @apollo/client graphql react

We can add the InMemoryCache into our Apollo Client by writing:

import { ApolloClient, InMemoryCache, gql } from "@apollo/client";

const cache = new InMemoryCache();

const client = new ApolloClient({
  uri: "https://graphqlzero.almansi.me/api",
  cache
});

client
  .query({
    query: gql`
      {
        user(id: 1) {
          id
          name
        }
      }
    `
  })
  .then(console.log);

The client is created with the cache option, which we set to the InMemoryCache; the cached items will be in memory. Once we’ve done that, we can use the new InMemoryCache features that come with Apollo Client 3.

We can evict the cached items by calling:

cache.evict();

We can optionally pass in the cached object’s ID by writing:

cache.evict({ id: 'user' })

We can also add a field property of the object like so:

cache.evict({ id: 'user', fieldName: 'name'  })

The cache.gc method lets us do garbage collection on cached items. The object is determined to be reachable by tracing from the root to all the child references. Normalized objects that aren’t visited are removed.

To clear the unreachable cached items, we just call:

We made a custom demo for .
No really. Click here to check it out.

cache.gc();

Garbage collection can also be configured to retain some items. To retain the object with the ID 'user', for instance, we can write;

cache.retain({ id: 'user' })

We can configure how to deal with dangling references. When an object is evicted from the cache, it might have objects that have other cached objects. Apollo Client preserves these references because they may still be used later.

We can change how these references are dealt with by using a custom read function. To do so, we would write:

import { ApolloClient, InMemoryCache, gql } from "@apollo/client";

const cache = new InMemoryCache({
  typePolicies: {
    Query: {
      fields: {
        ruler(existingRuler, { canRead, toReference }) {
          return canRead(existingRuler)
            ? existingRuler
            : toReference({
                __typename: "user",
                name: "Apollo"
              });
        }
      }
    },

    user: {
      keyFields: ["name"],
      fields: {
        offspring(existingOffspring, { canRead }) {
          return existingOffspring ? existingOffspring.filter(canRead) : [];
        }
      }
    }
  }
});

const client = new ApolloClient({
  uri: "https://graphqlzero.almansi.me/api",
  cache
});

client
  .query({
    query: gql`
      {
        user(id: 1) {
          id
          name
        }
      }
    `
  })
  .then(console.log);

We set the ruler of the cache to our own ruler function. We determine what references can be read.

If there’s an existing cache ruler, then we use it; otherwise, we get the item with toReference. The offspring method returns the objects where canRead returns true. This way, we know we can read those items.

Managing local state

We can create our own local field within the InMemoryCache object.

For instance, we can write:

import { ApolloClient, InMemoryCache, gql } from "@apollo/client";

const cache = new InMemoryCache({
  typePolicies: {
    User: {
      fields: {
        age: {
          read(_, { variables }) {
            return Math.random() * 100;
          }
        }
      }
    }
  }
});

We created a local field with name age. This way, we can include the field in our query like the loading state and the networkStatus. variables have the fields from the query. It also has the caching data.

It’s just a getter that returns a random number:

import { ApolloClient, InMemoryCache, gql } from "@apollo/client";

const cache = new InMemoryCache({
  typePolicies: {
    User: {
      fields: {
        age: {
          read(_, { variables }) {
            return Math.random() * 100;
          }
        }
      }
    }
  }
});

const client = new ApolloClient({
  uri: "https://graphqlzero.almansi.me/api",
  cache
});

client
  .query({
    query: gql`
      {
        user(id: 1) {
          id
          name
          age @client
        }
      }
    `
  })
  .then(console.log);

We get the age field with age @client. The @client keyword distinguishes local fields from fields that are retrieved from the API.

Reactive variables is the new feature from Apollo Client 3.0 onwards. To create one, we use the makeVar method from the @apollo/client package. For instance, to create a children reactive variable, we can write:

import { makeVar } from "@apollo/client";

const children = makeVar(["jane", "mary"]);

It returns a function that has the value of the reactive variable. To call it and get the value, we can write:

console.log(children());

The console log should read:

["jane", "mary"]

Reactive variables are useful for storing local state outside of the Apollo Client cache. This is different from local states and cached items, which are retrieved from the cache. Modifying a reactive variable automatically triggers an update of all active queries that depend on the variable.

We can also store local state with reactive variables. To do that, we can write:

import { ApolloClient, InMemoryCache, gql, makeVar } from "@apollo/client";

const age = makeVar(Math.random() * 100);

const cache = new InMemoryCache({
  typePolicies: {
    User: {
      fields: {
        age: {
          read(_, { variables }) {
            return age();
          }
        }
      }
    }
  }
});

const client = new ApolloClient({
  uri: "https://graphqlzero.almansi.me/api",
  cache
});

client
  .query({
    query: gql`
      {
        user(id: 1) {
          id
          name
          age @client
        }
      }
    `
  })
  .then(console.log);

Above, we created the age reactive variable, and we read it into the local state by returning it in the read method. Then we can query age like we do with other local states. Now, whenever our query changes, we’ll see a new value of age returned as well.

To update the reactive variable, we just pass in a new value, like so:

import { makeVar } from "@apollo/client";

const age = makeVar(Math.random() * 100);

console.log(age());

age(Math.random() * 100);

console.log(age());

We pass in a new value to the function returned by makeVar to update the value. Now both console logs should show different values.

Cache field policies

We can define our own cache field policy so that we can read them in a way that’s different from what’s in the API.

For instance, we can write:

import { ApolloClient, InMemoryCache, gql } from "@apollo/client";

const cache = new InMemoryCache({
  typePolicies: {
    User: {
      fields: {
        name: {
          read(name) {
            return name.toUpperCase();
          }
        }
      }
    }
  }
});

const client = new ApolloClient({
  uri: "https://graphqlzero.almansi.me/api",
  cache
});

client
  .query({
    query: gql`
      {
        user(id: 1) {
          id
          name
        }
      }
    `
  })
  .then(console.log);

We created a type policy for the User type. fields has the fields we want to modify when reading, and we want the value of name to be upper-case.

So we make the name‘s read method return the upper-case name. Now the console.log call in the then method should have the data field with user.name inside it being upper-case.

We can use this for many other applications, like setting default field values, transforming lists, changing field values, pagination, and much more.

Conclusion

Apollo Client 3 comes with many changes to caching, including the ability to clear cache data. We can also add local fields and change the ways normal fields are retrieved with cache policy.

You come here a lot! We hope you enjoy the LogRocket blog. Could you fill out a survey about what you want us to write about?

    Which of these topics are you most interested in?
    ReactVueAngularNew frameworks
    Do you spend a lot of time reproducing errors in your apps?
    YesNo
    Which, if any, do you think would help you reproduce errors more effectively?
    A solution to see exactly what a user did to trigger an errorProactive monitoring which automatically surfaces issuesHaving a support team triage issues more efficiently
    Thanks! Interested to hear how LogRocket can improve your bug fixing processes? Leave your email:

    : Full visibility into your web apps

    LogRocket is a frontend application monitoring solution that lets you replay problems as if they happened in your own browser. Instead of guessing why errors happen, or asking users for screenshots and log dumps, LogRocket lets you replay the session to quickly understand what went wrong. It works perfectly with any app, regardless of framework, and has plugins to log additional context from Redux, Vuex, and @ngrx/store.

    In addition to logging Redux actions and state, LogRocket records console logs, JavaScript errors, stacktraces, network requests/responses with headers + bodies, browser metadata, and custom logs. It also instruments the DOM to record the HTML and CSS on the page, recreating pixel-perfect videos of even the most complex single-page apps.

    .
    John Au-Yeung I'm a web developer interested in JavaScript stuff.

    Leave a Reply