🚀 Shiny New Apollo 2.1 API and apollo-link-state
In this article we’re going to learn how to write a modern GraphQL app that utilises some of the awesome new APIs recently announced by the Apollo team. Rather than starting from scratch, we’re going to build upon and refactor our counter app example from our previous exploration of apollo-link-state. To remind yourself of where we got to last time, have a play with this Snack.
Query Components
Query components are one of the brand-spanking-new features of Apollo 2.1. The key differences are that we move our components from being statically declared components wrapped with a GraphQL Higher-Order Component (HOC) to using the new dynamic Query component API.
Query components are a more dynamic way of using GraphQL in your application. They are an example of the Render Prop pattern which is becoming an idiomatic way of composing React components as an alternative to using a HOC.
As a refresher, lets take a look at our previous Counter
component:
const Counter = graphql(gql`
{
counter @client {
value
}
}
`)(
class extends Component {
render() {
return (
<View style={styles.container}>
<Text style={styles.counterText}>{`🐑 Counter: ${
this.props.data.counter.value
}`}</Text>
<IncrementButton />
</View>
);
}
}
);
As we can see this example still uses the static graphql
HOC. Let’s see what this looks like once we’ve refactored it to use the new Query
component:
const COUNTER_QUERY = gql`
{
counter @client {
value
}
}
`;
class Counter extends Component {
render() {
return (
<Query query={COUNTER_QUERY}>
{({ data }) => (
<View style={styles.container}>
<Text
style={
styles.counterText
}>{`🐑 Counter: ${data.counter.value}`}</Text>
<IncrementButton />
</View>
)}
</Query>
);
}
}
The Query
component accepts a function as a child. This function receives a few arguments which are essentially the same as those that would normally be injected as props if we were to use the static HOC instead. Since we’re only dealing with local state and a simple synchronous resolver we don’t need to worry about the loading
or error
arguments here. For more information on the available arguments I recommend checking out the official API documentation.
Hakuna Mutator 🦁
In response to my previous article, Daniel K. came up with some very valid points about why you might not want to use apollo-link-state
for managing your local state.
One of the main concerns with apollo-link-state
was the volume of boilerplate required to bootstrap our simple example. Let’s remind ourselves of what our original implementation looked like:
const stateLink = withClientState({
cache,
resolvers: {
Mutation: {
incrementCounter: (_, args, { cache }) => {
const { counter } = cache.readQuery({
query: gql`
{
counter {
value
}
}
`
});
const data = {
counter: {
__typename: "Counter",
value: counter.value + 1
}
};
cache.writeData({ data });
return null;
}
}
},
defaults: {
counter: {
__typename: "Counter",
value: 1
}
}
});
...
const IncrementButton = graphql(gql`
mutation incrementCounter {
incrementCounter @client
}
`)(
class extends Component {
onIncrementPressed = () => {
this.props.mutate({});
};
render() {
return <Button title="Increment" onPress={this.onIncrementPressed} />;
}
}
);
Now let’s take a look at how a quick refactor to use the new Apollo 2.1 API could simplify things by removing the need for standalone resolvers for simple mutations:
class IncrementButton extends Component {
onIncrementPressed = (cache, queryData) => {
const currentValue = queryData.counter.value;
const data = {
counter: {
__typename: 'Counter',
value: currentValue + 1,
},
};
cache.writeData({ data });
};
render() {
return (
<Query query={COUNTER_QUERY}>
{({ client, data }) => (
<Button
title="Increment"
onPress={() => this.onIncrementPressed(client, data)}
/>
)}
</Query>
);
}
}
As you can see we’ve actually removed the need for a formal GraphQL mutation altogether which has reduced the amount of boilerplate required. As a result, our local resolver configuration has also been condensed and we now only need to provide some default values for our data:
const stateLink = withClientState({
cache,
defaults: {
counter: {
__typename: 'Counter',
value: 1,
},
},
});
Tip: An alternative to the above we could just imperatively insert this default data into the cache on app launch. I think the declarative approach seen above is a bit neater!
I don’t wish to dictate the strategy you should use to manage your state - there are just so many considerations - you always need to be pragmatic and choose the best strategy that solves the problems that you have in your specific application. However, you can see that the new Apollo 2.1 APIs have opened up some additional options for managing local state and potentially reducing the amount of boilerplate you write.
With the use of the Query
component and directly writing to the cache seen above, we’ve already refactored the counter app over to Apollo 2.1! Check out this Snack to play around with the fully refactored example.
Bonus: Thanks to Raj Nigam for providing a CodeSandbox version so that you can also try it directly in your web browser.
Wrapping Up
In this article we’ve refactored our simple counter app example to leverage some of the new APIs available in Apollo 2.1.
We’ve actually only really scratched the surface and there are more things to explore: it’s definitely worth taking a look at the new Mutation
and ApolloConsumer
components to see how you might use them in your own applications.
The new Query
components are a another new tool in your toolbox for building applications. There’s nothing wrong with the former HOC strategy and there are many interesting discussions online about when to use each technique - I encourage you to read lots of material on the subject and draw your own conclusions.
It’s clear that GraphQL is a transformative technology and the Apollo stack is a great application of it. I’m excited about to see what’s next out of the GraphQL and Apollo pipelines 🚀