Manageable Front-end State with Apollo Client and React

Apollo Client is a library that helps you manage data in frontend applications built on top of well-known and respected Redux (predictable state container).
If you were to start using it today, would you know how best to approach it?
GraphQL is a query language for API. It’s quite a new technology. For this reason, there are a lot of new libraries that try to resolve the most common use cases and help developers focus on the business logic. Apollo Client focuses on reducing the boilerplate to a minimum, yet it is fully functional and has support for great features such as server-side rendering, optimistic UI or support for WebSockets.
There are a lot of articles about how to tackle GraphQL on backend - if you’re interested in that topic, you can check out this guide.
Getting Started with React and Apollo Client
As we’re only focusing on the frontend part of the application, we’ll use a server example provided by next.js. It’s built with a graphql-server package from Apollo Team but remember: the client is agnostic about the server implementation as long as it follows the GraphQL specs. The server is also configured to use hybrid WebSocket Transport. GraphQL specifies three operation types: queries (fetching data), mutations (modifying data) and subscriptions (pushing live updates to the client). Hybrid WS transport means that queries and mutations are going to be handled by regular HTTP interface and subscriptions are going to utilize WebSockets.
As for our frontend stack, we’ll base from next.js. However, we need to modify our config to support websockets:
import { ApolloClient, createNetworkInterface } from 'react-apollo'
import { SubscriptionClient, addGraphQLSubscriptions } from 'subscriptions-transport-ws';
let networkInterface = createNetworkInterface({
uri: 'http://localhost:8080/graphql',
opts: {
credentials: 'same-origin'
}
});
if(process.browser) {
const wsClient = new SubscriptionClient(`ws://localhost:5000/`, {
reconnect: true
});
networkInterface = addGraphQLSubscriptions(
networkInterface,
wsClient
);
}
function _initClient (headers, initialState) {
return new ApolloClient({
initialState,
ssrMode: !process.browser,
dataIdFromObject: result => result.id || null,
networkInterface: networkInterface,
})
}
We are injecting a WebSocket transfer protocol to our network interface handler so that the React Apollo knows how to deal with subscriptions. That’s all the configuration we need to start writing our business logic.
Why Should I Bother with Subscriptions?
Subscriptions are getting more and more popular across modern web apps. It’s because users appreciate interactive interfaces and implementation has become easier than before. There is no longer a reason to go for hacky solutions like polling or manual re-fetching triggered by, for example, mouse movement. Since GraphQL abstracted away from the transport interface, we no longer face the problem where most of our API lived in REST controllers and WebSocket message handlers were built using custom implementations.
Where Should I Use Subscriptions?
- In the part of your application where you think real-time updates would be crucial for the user experience, i.e. a chat component.
- For incremental updates. Let’s say you have a bunch of data that is initially quite big and you want to update it in time. Without subscriptions you would re-fetch all the data every time you wanted to update it. That would cause massive net overhead since most of that has already been acquired - it’s unnecessary redundancy. Subscription approach is different. An initial state is seeded from the query just like before. What differs is the fact that updates are pushed to the client who receives the diff. The only thing you need to worry about is merging the last update into the state, which is hassle-free.
Injecting Data into a React Component - the Higher Order Component Way
The HoC approach is very popular among React developers and it has been adapted by Apollo Client dev team. Let’s built a simple HoC that will inject messages (say: chat messages) to a component.
import { gql, graphql } from 'react-apollo';
const messagesQuery = gql`query { messages { id, content, user { nick } likes { nick } } }`;
export const withMessages = Component =>
graphql(messagesQuery)(Component);
That’s all we need to inject server data into a wrapped component. From within, we can consume it like a pro:
const { data: { messages, loading, error, refetch }, ...rest } = this.props;
The data is loaded whenever the component mounts. The possibility to re-fetch data is also injected automatically. We can even start polling in componentDidMount and stop it in componentWillUnmount react life-cycle methods. Apollo Client is even smart enough to fetch the data if more than one component utilizes the same part of data. Did I mention that the messages are already acquired on the server side?
This approach for handling data feels very similar to what react-redux can offer. In the end, react-apollo uses redux underneath.
Extending our HoC with Live Updates Functionality - Subscriptions
We start from writing our subscription query that describes the data that we are willing to receive from the server:
const newMessagesSubscription = gql`subscription onMessageSent {
messageSent { id content user { nick } likes { nick } }
Now, we need to extend our withMessages HoC so that it injects subscription prop to our component:
const withMessages = Component =>
graphql(messagesQuery, {
props: props => {
return {
...props,
subscribeToNewMessages: params => {
return props.data.subscribeToMore({
document: newMessagesSubscription,
updateQuery: (prev, { subscriptionData }) => {
if (!subscriptionData.data) {
return prev;
}
const newMessage = subscriptionData.data.messageSent;
return Object.assign({}, prev, {
messages: [...prev.messages, newMessage]
});
}
});
}
};
}
})(Component);
We’ve basically added a new method called subscribeToNewMessages that can be called from within our component. This method needs to meet a few constraints:
- It has to call subscribeToMore function with a configuration object that:
- Sets the document parameter to graphql subscription.
- Sets the updateQuery function that takes the previous data along with the upcoming data and handles the merge.
In our case, the updateQuery function adds consecutive messages to the messages’ object. Now we have to call subscribeToNewMessages() method from the component life-cycle method (componentWillMount preferably).
Sum-up
GraphQL itself is very powerful, but you may know that already. You might ask the question: “Why do I need Apollo Client to handle the data layer in my frontend application?” As I’ve shown above, it significantly reduces boilerplate, abstracts away from the transport protocol so that you could switch over to WebSockets, and greatly benefits from real-time data updates - all that in just about 15 lines of code. Just imagine implementing that with redux-saga on the frontend and Socket.IO/pusher on the backend. Have I convinced you now?