GraphQL Apollo Subscriptions for Realtime Updates in React
GraphQL Subscriptions are used to push data from server to client. The client can subscribe to events to listen to an update as and when data in the server changes. And therefore, first of all, we need to implement subscriptions on the server side to publish events and thus notify the client regarding updates. We already did this in the last tutorial. In this section, we will look after the UI updates in our react application.
# How to Autorefresh React App with GraphQL?
We have already built a react app and components for CRUD operation using GraphQL API. The problem with this application, for now, is that user needs to manually refresh the browser to use any CRUD changes. This affects UX to a great extent. We will take forward the same app forward and wire subscriptions to it so that user can see updates right away without any intervention.

# WebSocket Setup
First of all, we will install a third-party library that can help us wire apollo subscriptions and web socket:
npm install subscriptions-transport-ws@0.8.2 --save
To get realtime updates from the server, we need a socket connection between client and server for faster communication. That is, subscription queries must run on a web socket while the normal queries and mutations can run over HTTP.
# WebSocket Implementation
Let us now wire the installed library within our root component app.js
:
# src/app/app.js
...
import { SubscriptionClient, addGraphQLSubscriptions } from 'subscriptions-transport-ws';
const wsClient = new SubscriptionClient(`ws://localhost:7900/subscriptions`, {
reconnect: true
});
Here we are importing and initializing the client from GraphQL subscriptions transport. We also mention the URI link for WebSocket
subscriptions.
const networkInterfaceWithSubscriptions = addGraphQLSubscriptions(
networkInterface,
wsClient
);
Next, we are wiring the newtworkInterface
(for HTTP Req) and wsClient
(for WS Req) together.
# src/app/app.js
...
//remove
networkInterface.use([{
applyMiddleware(req, next) {
setTimeout(next, 1000);
},
}]);
We remove the middleware since we don’t really need to implement the middleware separately for HTTP endpoint.
# src/app/app.js
...
//add
const client = new ApolloClient({
networkInterface : networkInterfaceWithSubscriptions
});
And now, clients can utilize both the endpoints that we declared in networkInterfaceWithSubscriptions
in the whole application.
Here’s our final app.js
:
# src/app/app.js
import React from 'react';
import { render } from 'react-dom';
import { BrowserRouter, Route, Switch } from "react-router-dom";
import { ApolloClient, ApolloProvider, createNetworkInterface } from 'react-apollo';
import { SubscriptionClient, addGraphQLSubscriptions } from 'subscriptions-transport-ws';
import CompaniesList from './components/list_company';
import CreateCompany from './components/create_company';
import DisplayCompany from './components/display_company';
import EditCompany from './components/update_company';
import '../style/app.scss';
const networkInterface = createNetworkInterface({
uri: 'http://localhost:7700/graphql',
});
const wsClient = new SubscriptionClient(`ws://localhost:7700/subscriptions`, {
reconnect: true
});
const networkInterfaceWithSubscriptions = addGraphQLSubscriptions(
networkInterface,
wsClient
);
const client = new ApolloClient({
networkInterface : networkInterfaceWithSubscriptions
});
let app = document.querySelector('#app');
render(
<ApolloProvider client={client}>
<div className="App">
<div className="row">
<div className="col-lg-6 col-lg-offset-3">
<BrowserRouter>
<div>
<Switch>
<Route path="/company/create" component={CreateCompany} />
<Route path="/company/:cId/update" component={EditCompany} />
<Route path="/company/:postId" component={DisplayCompany} />
<Route path="/" component={CompaniesList} />
</Switch>
</div>
</BrowserRouter>
</div>
</div>
</div>
</ApolloProvider>,
app
)
We can check this in a browser by inspecting the network elements:

# GraphQL Subscriptions
Let us now define the subscriptions so that the react app receive notifications and updated data. We will use subscriptions to listen to changes caused during insert, update and delete operation:
Insert:
const companyAddSub = gql`
subscription companyAdded {
companyAdded {
id
name
}
}
`;
Update:
const companyEditSub = gql`
subscription companyEdited {
companyEdited {
id
name
}
}
`;
Delete:
const companyDeleteSub = gql`
subscription companyDeleted {
companyDeleted {
id
name
}
}
`;
companyAdded
, companyEdited
and companyDeleted
come from our server specifications in the schema.js
file.# Update React UI for GraphQL API
The react component automatically re-renders itself when any data changes. Thus all we have to concentrate on is to tweak the data when any subscription event occurs. Also, here’s the complete list_company
component for reference. Don’t feel overwhelmed; I’ll walk you through each component componentWillReceiveProps
in a moment as we have already discussed the rest of the part.
# src/app/components/list_company.js
import _ from 'lodash';
import React, { Component } from 'react';
import { gql, graphql, compose } from 'react-apollo';
import { Link } from "react-router-dom";
class CompaniesList extends Component {
componentWillReceiveProps(nextProps) {
if (!this.subscription && !nextProps.data.loading) {
let { subscribeToMore } = this.props.data
this.subscription = [subscribeToMore(
{
document: companyAddSub,
updateQuery: (previousResult, { subscriptionData }) => {
const newCo = subscriptionData.data.companyAdded;
if (!previousResult.Companies.find((c) => c.id === newCo.id)) {
let updatedList = Object.assign({}, previousResult, { Companies :[...previousResult.Companies, newCo ] });
return updatedList;
} else {
return previousResult;
}
},
}),
subscribeToMore(
{
document: companyEditSub,
updateQuery: (previousResult, { subscriptionData }) => {
const editCo = subscriptionData.data.companyEdited;
if (previousResult.Companies.find((c) => c.id === editCo.id)) {
let updatedList = Object.assign({}, previousResult, { Companies :[...previousResult.Companies ] });
return updatedList;
} else {
return previousResult;
}
},
}),
subscribeToMore(
{
document: companyDeleteSub,
updateQuery: (previousResult, { subscriptionData }) => {
const delCo = subscriptionData.data.companyDeleted;
const oldList = previousResult.Companies;
let newList = oldList.filter(function (elem) {
return elem.id !== delCo.id
});
let updatedList = Object.assign({}, previousResult, { Companies: newList });
return updatedList;
},
})
]}
}
handleDelete (cId) {
this.props.DeleteCo({variables: { id: cId }})
.then(console.log('deleted'));
}
render() {
const { loading, error, Companies }= this.props.data;
if (loading) {
return <p> Loading... </p>;
}
if (error) {
return <p> { error.message } </p>;
}
return (
<div>
<div className="text-right">
<Link className="btn btn-primary" to="/company/create">
Add a Company
</Link>
</div>
<ul className = "list-group" > {
Companies.map(c => <li className = "list-group-item" key = { c.id }>
<div className="company-list">
<div className="company-name">
<Link to = {`/company/${c.id}`}>
{c.name}
</Link>
</div>
<div className="company-btn">
<Link className="btn btn-primary" to={`company/${c.id}/update`}>
Update
</Link>
<button type="submit" className="btn"
onClick={() => this.handleDelete(c.id)}>Delete</button>
</div>
</div>
</li>
) }
</ul>
</div>
);
};
}
static fragments = {
pokemon: gql`
fragment PokemonCardPokemon on Pokemon {
url
name
}
`
}
const CompanyListQuery = gql `
query CompanyList{
Companies {
id
name
}
}
`;
const DeleteCo = gql`
mutation deleteCompany($id: ID!) {
deleteCompany(id: $id) {
id
name
}
}
`;
const companyAddSub = gql`
subscription companyAdded {
companyAdded {
id
name
}
}
`;
const companyEditSub = gql`
subscription companyEdited {
companyEdited {
id
name
}
}
`;
const companyDeleteSub = gql`
subscription companyDeleted {
companyDeleted {
id
name
}
}
`;
const CompanyListWithMutations = compose(
graphql(CompanyListQuery),
graphql(DeleteCo, {
name: 'DeleteCo'
})
)(CompaniesList)
export default CompanyListWithMutations;
UI Updates for Insert Operation Subscription Event:
We will do this in react’s life cycle componentWillReceiveProps
method. The subscriptions-transport-ws
library sends the updated data in data.subscribeToMore
props. Let us scaffold the method:
componentWillReceiveProps(nextProps) {
if (!this.subscription && !nextProps.data.loading) {
let { subscribeToMore } = this.props.data
this.subscription = [subscribeToMore(
{
document: companyAddSub,
updateQuery: (previousResult, { subscriptionData }) => {
const newCo = subscriptionData.data.companyAdded;
//more logic
}
})
]
}
}
In the above code snippet, we are first validating the subscription for new updates and loading prop
. Next, using es6, we are de-structuring the subscribeToMore
prop from this.props.data
and passing arguments to it. document
depicts the subscription name we intend to listen to. We declared this in the previous step. updateQuery
is used to update the data store. previousResult
refers to the old/current stored data while subscriptionData
is the new data pushed by the server for subscribers.
# src/app/components/list_company.js
...
class CompaniesList extends Component {
componentWillReceiveProps(nextProps) {
if (!this.subscription && !nextProps.data.loading) {
let { subscribeToMore } = this.props.data
this.subscription = [subscribeToMore(
{
document: companyAddSub,
updateQuery: (previousResult, { subscriptionData }) => {
const newCo = subscriptionData.data.companyAdded;
if (!previousResult.Companies.find((c) => c.id === newCo.id)) {
let updatedList = Object.assign({}, previousResult, { Companies :[...previousResult.Companies, newCo ] });
return updatedList;
} else {
return previousResult;
}
}
})
]
}
}
}
As a precaution, we first check whether the subscription has any data payload. subscriptionData.data.newPro
has the fresh data from the server . (Note – newPro
is the subscription name described in schema
)
Since both the old store and fresh data set are read-only, we return a fresh object and append the new dataset that user inserted after checking for duplicate values with id
.
Testing:
You can now insert a new company and see the instant updates:
http://localhost:8100

UI Updates for Update Operation Subscription Event:
Since we will work on companyEditSub
subscription, we will mention it as a document
key.
subscribeToMore(
{
document: companyEditSub,
updateQuery: (previousResult, { subscriptionData }) => {
const editCo = subscriptionData.data.companyEdited;
if (previousResult.Companies.find((c) => c.id === editCo.id)) {
let updatedList = Object.assign({}, previousResult, { Companies :[...previousResult.Companies ] });
return updatedList;
} else {
return previousResult;
}
},
}),
Here we are checking for an opposite condition than insert event. We are finding the correct id
from the old data store and returning a fresh object that has the updated data.
Testing:
You can now update an existing product and see the instant updates:
http://localhost:8100
UI Updates for Delete Operation Subscription Event:
We will begin with mentioning the document
name we intend to work with and that is proDelete.
subscribeToMore(
{
document: companyDeleteSub,
updateQuery: (previousResult, { subscriptionData }) => {
const delCo = subscriptionData.data.companyDeleted;
const oldList = previousResult.Companies;
let newList = oldList.filter(function (elem) {
return elem.id !== delCo.id
});
let updatedList = Object.assign({}, previousResult, { Companies: newList });
return updatedList;
},
})
Here we are first removing the deleted company from the old data store and passing it to the fresh object.
Testing:
You can now delete an existing product and see the instant updates:
http://localhost:8100

Conclusion:
GraphQL subscriptions enable realtime updates in the application. We utilized them in our react application to let users browse the application smoothly. Our client (react application) subscribed to the relevant events for this purpose and were notified via a web socket connection. We used third party library to configure the whole setup and then worked around wiring them in the components. We also tested the app for realtime updates and thus completed the UI cycle.
Questions or Comments:
Some gross stuff, isn’t it? If you face any issue in implementing GraphQL Apollo Subscriptions in React Application, comment them below.