Migrating to Apollo Client 3.0
Adam C. |

We chose Apollo Client two years ago for a React project. The package we used originally was apollo-boost, later in order to support upload, we moved to apollo-boost-upload, which is just apollo-boost + apollo-link-upload, nothing needs to be changed in our original code. And we also used react-apollo in order to use <Query> and <Mutation> components.

Photo by Brian McGowan on Unsplash

Since the Apollo-Boost project is now retired, and React-Apollo has been deprecated, it's time to migrate to Apollo Clients 3.0.

Apollo Clients package includes both React hooks and Graphql request handling, so we don't need to do install separate packages, like Apollo-Boost and React-Apollo. However, Apollo Client 3.0 is mainly focusing on Hooks when being used in the React project. And our React codebase is still mainly using the Class component, which would take some time to rewrite using Hook. So we decided to migrate to Apollo Client 3.0 without using Hook, and then granularly rewrite the code to using Hook.

There is not too much documentation about how to use Apollo Client 3.0 with React Class Component. But there is the way.

Please keep in mind:

In order to use Apollo Client 3.0 with React Class Component, we have to use some depreacted API, like React Components and HOC

ApolloClient Constructor

First of all, remove ‘Apollo-Boost’, in our case, it's “Apollo-Boost-Upload”.

// import ApolloClient from "apollo-boost-upload";	
// import { ApolloProvider } from 'react-apollo';
import { ApolloClient, ApolloProvider, InMemoryCache } from "@apollo/client";

If you don't use Authorization or Upload, then the main change would be manually setting up the cache. Here we will explain all of those changes.

Authorization Header

With Apollo-Boost, we can pass the authorization token in the ‘headers’ option like this:

 const client = new ApolloClient({
 	headers: {
    	Authorization: token,
    },

But in @apollo/client, we have to use apollo- link

import { ApolloLink } from "apollo-link";

const authLink = new ApolloLink((operation, forward) => {
  // Use the setContext method to set the HTTP headers.
  operation.setContext({
    headers: {
      authorization: token ? `Bearer ${token}` : "",
    },
  });

  // Call the next link in the middleware chain.
  return forward(operation);
});

Enable Upload

By default, Apollo Client does not allow FileList, File, Blob, or ReactNativeFile instances within query or mutation variables and sends GraphQL multipart requests. So we need apollo-upload-client to support this.

import { createUploadLink } from "apollo-upload-client";

const uploadLink = new createUploadLink ({ uri: YOURAPIURI});

Note that, when we use apollo-boost, we pass in uri. But when we use @apollo/client, we can pass in link or uri. The difference between link and uri is that the former allows us to compose actions around data, i.e., we can chain different links together to implement complex data handling logic. See the diagram below:

 

Chainable Apollo Link- Photo from ApolloGraphql

In our case, we could chain authLink and uploadLink, like below:

const link = ApolloLink.from([
  authLink,
  uploadLink
]);

const client = new ApolloClient({
  // uri: nccihAPI,
  link: link,
  cache: cache,
  typeDefs,
  resolvers,
});

Cache

You probably see the cache in the above ApolloClient constructor. When we use apollo-booster, it integrates InMemoryCache for us, so there is no need to pass in the cache option. But when use @apollo/client, we have to manually create this. Below is how to create a cache using InMemoryCache with some local states:

const localStateValue = {
  userId,
  displayName,
};

const cache = new InMemoryCache({
  typePolicies: {
    Query: {
      fields: {
        currentUser: {
          read() {
            return localStateVar();
          },
        },
      },
    },
  },
});

const localStateVar = cache.makeVar(localStateValue);

ApolloProvider

To migrate to ApolloClient 3, we only need to import ApolloPrivder from "@apollo/client" instead of “react-apollo”. We still use ApolloProvider like before in the top component, in our case, App.js

<ApolloProvider client={this.state.client}>
	<div className="App container">
    	<Switch>
        	<Route path="/preview/:pageType/:pageId" component={Preview} />
           	<Route path="/*" component={Dashboard} />
        </Switch>
    </div>
</ApolloProvider>

Query & Mutation

Since we are still using the Class component, we cannot take an advantage of Hook (useQuery and useMutation.) In ApolloClient 3, we can continue to use <Query> and <Mutation> component, and it works pretty much the same way as that in react-apollo. We only need to import them from @apollo/client package, like:

import { Query, Mutation } from "@apollo/client/react/components";

Manually firing a Query

When use Query component, Apollo Client automatically fires off our query, but sometimes, we wanted to manually fire a query until the user performs an action. In that scenario, we need to be able to access Apollo Client in the child component. When use react-apollo, we can use ‘withApollo’, a high order function, wrapping the child component to make this.props.client available in the child component, like this:

import { withApollo } from "react-apollo";

class CustomPageList extends Component {
	handleDelete = async (e, item, customPageList) => {
    	const { data } = await this.props.client.query({
      		query: GET_IsPageUsedAsHighlightFeatured,
      		fetchPolicy: "no-cache",
      		variables: { uuid: item.uuid },
    	});
    });
}
export default withApollo(CustomPageList);

We could still use HOC to pass in Apollo Client, but it becomes a little bit complexly. To do that, we should use ApolloConsumer, like this:

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

const WithApolloCustomPageList = (props) => (
  <ApolloConsumer>
    {(client) => {
      return <CustomPageList {...props} client={client} />;
    }}
  </ApolloConsumer>
);

export default WithApolloCustomPageList; 

That's it!