Query Components with Apollo
A couple of months ago, we started to use Apollo Client (+React Native) at Werkspot. From the very beginning, Apollo overwhelmed us by the simplicity and flexibility it provides. It inspired us to create our own vision about technology and experiment with various approaches to data fetching that we think are worth sharing.
Enjoy the ride! 🚀
Anatomy of Query Components
One of the very powerful concepts that we found for ourselves with Apollo is Query Components. These components are meant to express a domain-specific data logic in a clear, declarative way:
CitiesQuery
component is an oversimplified example of an average Query Component. It exports a Component
, enhanced by a graphql
wrapper for fetching data. After Apollo takes care of fetching and mapping props to query parameters, query component uses “function-as-a-child” approach to expose a child-independent API with injected data.
Comparing to the standard approach where you add a graphql
wrapper to your higher-order components, query components have a set of advantages:
- Testability. Unless you have a very good reason, you can test your components without any data management logic.
- Separation of concerns. From now on, higher-order components are no longer concerned about the way data is fetched/pushed to the server.
- Abstraction over data management layer. Frankly speaking, it doesn’t really matter which library you use under the hood. Query Components abstracts out your data management layer so you can focus on business logic and change underlying implementation if needed.
Once a Query Component is defined, it can be used as any other component in your application:
A nice side effect of decoupling higher-order components into Query Components is minimizing bloat. If the data management layer grows, it won’t affect presenter components unless they have to reflect new data properties.
Mutations with Query Components
Mutations are not that straightforward as queries. When a query component is mounted, Apollo immediately tries to fetch. However, when we talk about mutations, there is no need (in 99% of our cases) to perform any mutations at the mount time. This idea brings us to the point that we should have a way to control a moment when we execute our mutations. For example:
Therefore, you can use this component in a similar way:
So the concept is the same: Query Component receives a bunch of props that are later on used as parameters for the mutation query.
How to test Query Components?
When it comes to testing, Apollo-based components have a bunch of features that we need to take into account. The most complicated one is related to React context and Apollo Client reference in particular. All components that intent to use Apollo should be put into the subtree of <ApolloProvider />
which gives them a reference to the client through the React context. Hopefully, if we use Enzyme and shallow rendering, we can supply context object as one of the options.
Assertions
One of the very first things we would like to test is that every Query Component is able to produce variables, required by a query. To ensure that, we check a shape of resulting variables.
Another assertion that we would like to make is about response shape. In other words, we check if given data.x
complies with the shape of the mock that was passed to the fake client during initialization.
And the last, but not least: query component should call a function that we pass as a child with the data pulled from Apollo. Altogether, the test suit looks like this:
The very last uncovered function is makeFakeClient
. It is a self-written helper that generates an Apollo Client with a fake link:
This should be more or less it. I didn’t cover mutation components testing, but it isn’t anyhow different. The only thing I can foresee is an additional assertion that execute
handler triggers Apollo fetcher, but I’ll leave it to the reader.
Where to go from here
Don’t hesitate to ask questions, challenge this approach and share your ideas about Apollo and GraphQL overall. You can always find me on Twitter or Discord (kureev
on #reactiflux
).
Oh yeah, and last but not least: if you like this or any other of my articles, don’t hesitate to become a part of it! Your support is greatly appreciated!