When, Why, and How to Use GraphQL with Apollo Client in React
- 1125486 views
- 15 min
- Feb 04, 2020
GraphQL is a new API standard that’s a more effective, powerful, and agile substitute for the REST standard. GraphQL was developed as an open-source project in 2015 by Facebook. Today, the GraphQL language has a massive community of fans all over the world.
This language provides declarative data fetching, meaning a client can specify exactly what data they need from an API. GraphQL is a query language for APIs, not a query language for databases. A GraphQL server provides only one endpoint and responds with exactly the data the client has requested instead of providing several endpoints that return fixed data structures. Additionally, GraphQL simplifies the aggregation of data from different sources and uses specific types to describe queries and data.
In this article, we’ll walk you through the main terms you need to know to work with GraphQL and show you problems we’ve faced with GraphQL in our projects and ways to solve them.
To understand the architecture of GraphQL, we need to know its main components.
A schema is the only thing that defines an API’s capabilities and determines how clients can request data.
Resolvers are methods that return data for the current field.
Here’s how a schema with a resolve method looks in GraphQL.
A type is a way to describe data fields in GraphQL. This is what the main types in the GraphQL architecture look like:
Let’s talk about the GraphQL architecture and how it works. A client requests resources from a GraphQL server using a GraphQL query. Then the query is compared to the schema on the server according to its type. The GraphQL server analyzes the query, passes it recursively through the graph, and performs its resolver function for each field. When all requested data has been collected, the GraphQL server returns a response.
If you want to use third-party UI libraries to work with a server, the process will be similar. First, you’ll generate a query using a tool like Apollo Client. Then GraphQL will start to aggregate data from entirely different sources (database, microservices, cache, etc.) based on this query. Here’s a diagram of how it looks:
There are a lot of libraries you can use to work with GraphQL, including Relay, Prisma, URQL, and Apollo Client. We prefer to work with Apollo, as it has a large community of supporters, is frequently updated, and is compatible with iOS, Android, Angular, Ember, Vue, and React.
Apollo Client is a convenient client library for working with GraphQL. It can send queries, cache requested data, and update the state of UI components.
The Apollo Client library has a range of benefits:
- Declarative data selection. You can write a request and get data without needing to manually monitor downloads.
- Convenient instruments for development. You can take advantage of tools such as Chrome Apollo DevTools and VS Code plugins.
- Supports modern React and Hooks APIs.
- Universal compatibility. You can use any build settings and any GraphQL API.
- Strong community support. This library is very popular and has a lot of committers who improve it all the time.
Now that we’ve checked out the architecture of GraphQL and chosen this library to work with, let’s review the types of queries in GraphQL.
Types of queries in GraphQL
GraphQL has some specific types of requests for different actions:
- Query — request to read data
- Mutation — request to change data
- Subscription — request that receives or sends real-time event data
Let’s take a look at each of these request types in detail.
A query has a string structure that can contain parameters sent in the body of POST requests to the GraphQL endpoint. In response, we get a JSON file that contains either a data field with data we requested or an “error” field if something went wrong. For one request, we get one response.
Now let’s see how we can make requests to Apollo Client.
1.1 Query with client object
A query with a client object is convenient as an additional query in render mode. You can create a method in a component that will call a query and return a response. In the example below, the argument of a client function is the property of a query component in Apollo Client.
A client query can be executed as a side effect in a life cycle component. Apollo Client has a higher-order component (HOC) called withApollo, in which a client is the props of the wrapped component, as in the example below:
In this example, SOME_QUERY is a query string to fetch data.
Now let’s look at a request with a declarative description of query as a component.
1.2 Query with a declarative description of query as a component
As you can see, the query in the code above returns three properties: data, loading, and error. In our project, we also used the refetch property, which allows us to refetch an existing query and get updated data. However, these aren’t all the parameters the query can return. You can read more about this in the useQuery API section of the Apollo Docs.
In the code snippet above, we showed a simple example of using queries. But what if we have a more complex and interactive page? Imagine we have a page that contains a complex form for editing and is divided into sections, where each change is a commit to a database. As a result, you’ll have nesting according to the following structure.
This structure is extremely unreadable, especially because of the layout and custom component. We need to break this form into subcomponents. By doing so, we'll make the code more testable and readable. When taking a declarative approach, it’s better to use a hierarchy with at most two layers of nesting. Can we limit ourselves with the declarative approach to components? If you need to use a query in the lifecycle of React components, or if you need to pass requested data to a function that runs before the component is rendered, or if you have a composition of queries and mutations for one component, we recommend using high-order components.
1.3 Query with high-order components
In the example below, we show the concept of using GraphQL with a single query.
And here’s an example of using options, name methods, and config objects:
Here, COMPANY is a string to fetch data. There are also skip, props, withRef, and alias methods that can be described with a similar approach. You can read more about these methods in the Apollo Docs.
For us, it’s more interesting to take a look at the composition of queries and mutations. To do this, we need to import the compose method. Here’s how we do it:
Bear in mind that the compose method will be removed in React Apollo Client 3.0. That’s why it’s better to use the flowRight method from the lodash library. Here’s the proof.
Fetching data from the query looks like props of your components. Here’s an example:
Here’s how it looks for class components:
Or if you’re using functional components:
The list of returned parameters is similar to declarative Query from subsection 1.2
1.4 Queries with useQuery React Hooks
The new version of Apollo Client provides support for describing all query operations with the Hooks API. Hooks reduce the amount of code we need to work with data and make components cleaner. As a result, our code is much easier to understand. Developers don’t need to figure out how high-order components or nested declarative components and their hierarchies are arranged. For greater consistency with hook components, we recommend using Hook APIs if your project supports them.
The options contain all methods from the “query with the declarative description of Query as a component” subsection. If you need to add parameters to the request, it will look like this:
Here, NEW_EMAIL_REQUEST is a data query.
1.5 Delayed useLazyQuery query
We’re going to show you how to use a delay with the Hook API. Unlike useQuery, which is called when components are mounted, a request with a delayed call calls the request function only when an action is performed. For instance:
According to this code, when all users are loaded, the getNewUser function is called, and a new user is requested when we click the button. We can compare this with earlier versions of Apollo Client:
In earlier versions of the Apollo Client library, we additionally needed to use the ApolloConsumer client object. As you can see from the code snippets, hooks simplify this implementation.
A mutation is a request to change data that has the structure of a string. This string can be with or without parameters. It’s sent in the body of POST requests to the GraphQL endpoint.
As a response, we get a JSON file that contains either requested data in the data field or an error field. For one request, we get one response.
2.1 Mutation request with client
In this example, the argument of the client function is a Mutation property of the Apollo Client component. We can also access a client object using the withApollo HOC or the mutation properties. We’ll talk more about mutation options and properties in the next section.
2.2 Declarative description of a mutation
A declarative description is convenient and readable (in terms of the structure of the render props hierarchy) only at the first or maximum second level. This means we have a major mutation that confirms the form, and one element of this form contains one mutation. In all other cases, it’s better to use a mutation composition or Hook API. We’ll get back to this later.
We also want to show you the options for refetchQuery, which updates the Apollo Client cache after the mutation.
You can find a full list of mutation options and properties in the Mutation section of the Apollo Docs.
2.3 Mutation with high-order components
In the previous section, we saw that if we have more than three levels of nesting for declarative components, it’s better to use НОС to implement queries. Similar to Queries, Mutation requests also can be configured with an options object, like variables, name, and refetchQueries. Here’s an example of a mutation:
2.4 Request with the useMutation hook
The useMutation hook in React is the main API for performing mutations in an Apollo Client app. To start a mutation, we declare useMutation at the beginning of the React component and pass the GraphQL string (which represents the mutation) to the React component. When rendering a component, useMutation returns a function that we can call at any time to perform the mutation along with an object with fields that represent the current state of mutation execution. For example:
In our projects, in the mutation options, we also use the onCompleted and onError callbacks to resolve successful and unsuccessful mutations.
The third type of operation available in GraphQL is a subscription. There are cases when a client may want to get real-time updates from a server. A subscription allows us to catch GraphQL API events so we can modify data in real time. Data changes that clients can track are defined in the API as fields with a subscription type. Writing a GraphQL query to track a subscription is similar to defining other operations.
3.1 Description of a client subscription
In the example above, the argument of a client function is a subscription property of the Apollo Client component.
3.2 Declarative description of a subscription
Subscriptions are just listeners; they don’t request any data when they’re first connected, but they open a channel of connection for receiving data. Here’s how we can visualize data:
For a full list of all subscription sections, options, and properties, check the Subscription section of the Apollo Docs.
3.3 Subscriptions with the useSubscription hook
Visualizing the subscription model with hooks is more concise than declarative descriptions.
4. Request composition with the Hooks API
When we execute multiple requests in one component, the use of high-order components or the render props mechanism can make our code difficult to understand. The fact that we use the render prop API and create structures with nested Mutation and Query components gives the deceptive feeling that our code is well-structured and has a clear hierarchy. We can avoid using НОС graphQL and additional methods like flowRight by declaring a Mutation at the beginning of components. The new useMutation and useQuery hooks allow us to completely solve problems with a confusing hierarchy of render structures and eliminate the need to use HOCsHere, we used additional methods such as flowRight, graphql hoc, and Query imported from different dependencies in package.json. The code gets more complex and less readable if the actual contents of the return method are added. We can make the code cleaner and simpler with useQuery and useMutation.
We’ve gotten rid of unnecessary dependencies from other libraries, and the return method is clearer. The code is now far more structured and logical.
5. Queries with HTTP
Besides using canonical query types for receiving data, you can get data with the help of HTTP queries to GraphQL endpoints. Here’s how that looks:
We’ve described a query for fetching data in the query variable. The URL is the link by which the request will be executed. The variable opts contains options with which the request will be executed.
6. Mutation with HTTP
The variable mutate makes a request for getting a new accessToken. The variable URL is the link by which the request will be executed. The variable opts contains options with which the request will be executed.
In this section, we describe problems we’ve faced in our projects and demonstrate the solutions we’ve come up with when implementing different features.
Customization of HttpLink
In our project, we needed to customize an HttpLink object when implementing authorization and updating the user session token. The HttpLink object has a fetch method, which, by default, performs an HTTP request to receive or change data. We had to change the standard implementation of the fetch method. We can send mutations in the form of HTTP, as we mentioned, and thereby request or update an accessToken.
The implementation of our customFetch method looks like this:
While implementing this method, it was impossible to use the client object with a mutation because of a loop caused by the customFetch method of the HttpLink object.
Customization of FragmentMatcher
We needed to customize FragmentMatcher due to problems caused by queries of the Union type. In the console, we detected a “heuristic fragment matching going on!” warning.
Before digging into the cause of this problem, we need to realize that Apollo Client doesn’t know our remote GraphQL schema and therefore can’t accurately display fragments. This means that in the console, we’ll see one more error: “You are using the simple (heuristic) fragment matcher, but your queries contain union or interface types.”
Now let’s fix the cause of this warning.
Steps to get a remote GraphQL schema locally
#1 Request a schema
We need to request a GraphQL schema from the back end and save it locally.
We saved the file as src/scheme/fetchScheme.js. We need this file because in package.json, we will execute the command to update the schema in order to maintain its relevance.
#2 Pass localGqlScheme to InMemoryCache
Now we need to pass the fragmentMatcher object to the ApolloClient constructor.
#3. Build and request the scheme in localGqlScheme.json
We use yarn and Next.js in our project, request a schema, and build the project.
#4. Testing after implementing fragmentMatcher
In the previous section, we successfully implemented fragmentMatcher. However, all our tests will fail because the testing environment doesn’t know the schema we put in the cache.
When you make all the changes shown above, your tests will be successful because MockedProvider will work with the relevant cache. Remember that in every case when you need to test cases with mock queries of the Union type, you need to add __typename.
In all other cases without Union or Interface, the type name is optional. In the example above, you can see that the withTypeName properties can be configured.
In this article, we explained what GraphQL is, demonstrated the popularity of Apollo Client for working with GraphQL, showed how Apollo Client interacts with the UI and GraphQL, and, what’s most important, demonstrated various methods for describing requests and subscriptions in Apollo React Client. We shared with you not only numerous ways to describe types of operations but also our own ideas and experience gained in our projects.
If you have any comments or suggestions, start a conversation below.