GraphQL Subscriptions
GraphQL Subscriptions (GQLS) are a mechanism which allow clients to subscribe to changes in a piece of data from the server, and get notified whenever that data changes.
A GraphQL Subscription looks very similar to a query, with the exception that it uses the subscription keyword:
subscription FeedbackLikeSubscription($input: FeedbackLikeSubscribeData!) {
feedback_like_subscribe(data: $input) {
feedback {
id
like_count
}
}
}
- Subscribing to the above subscription will notify the client whenever the specified
Feedback
object has been "liked" or "unliked". Thefeedback_like_subscription
field is the subscription field itself, which takes specific input and will set up the subscription on the backend. feedback_like_subscription
returns a specific GraphQL type which exposes the data we can query in the subscription payload; that is, whenever the client is notified, it will receive the subscription payload in the notification. In this case, we're querying for the Feedback object with its updatedlike_count
, which will allows us to show the like count in real time.
An example of a subscription payload received by the client could look like this:
{
"feedback_like_subscribe": {
"feedback": {
"id": "feedback-id",
"like_count": 321,
}
}
}
In Relay, we can declare GraphQL subscriptions using the graphql
tag too:
const {graphql} = require('react-relay');
const feedbackLikeSubscription = graphql`
subscription FeedbackLikeSubscription($input: FeedbackLikeSubscribeData!) {
feedback_like_subscribe(data: $input) {
feedback {
id
like_count
}
}
}
`;
- Note that subscriptions can also reference GraphQL variables in the same way queries or fragments do.
There are two ways of executing a subscription against the server. The requestSubscription
API and using hooks.
Request subscription API​
In order to execute a subscription against the server in Relay, we can use the requestSubscription
API:
import type {Environment} from 'react-relay';
import type {FeedbackLikeSubscribeData} from 'FeedbackLikeSubscription.graphql';
const {graphql, requestSubscription} = require('react-relay');
function feedbackLikeSubscribe(
environment: Environment,
feedbackID: string,
input: FeedbackLikeSubscribeData,
) {
return requestSubscription(environment, {
subscription: graphql`
subscription FeedbackLikeSubscription(
$input: FeedbackLikeSubscribeData!
) {
feedback_like_subscribe(data: $input) {
feedback {
id
like_count
}
}
}
`,
variables: {input},
onCompleted: () => {} /* Subscription established */,
onError: error => {} /* Subscription errored */,
onNext: response => {} /* Subscription payload received */
});
}
module.exports = {subscribe: feedbackLikeSubscribe};
Let's distill what's happening here:
requestSubscription
takes an environment, thegraphql
tagged subscription, and the variables to use.- Note that the
input
for the subscription can be Flow-typed with the autogenerated type available from theFeedbackLikeSubscription.graphql
module. In general, the Relay will generate Flow types for subscriptions at build time, with the following naming format:*<subscription_name>*.graphql.js
. requestSubscription
also takes anonCompleted
andonError
callbacks, which will respectively be called when the subscription is successfully established, or when an error occurs.requestSubscription
also takes anonNext
callback, which will be called whenever a subscription payload is received.- When the subscription payload is received, if the objects in the subscription payload have IDs, the records in the local store will automatically be updated with the new field values from the payload. In this case, it would automatically find the existing
Feedback
object matching the given ID in the store, and update the values for thelike_count
field. - Note that any local data updates caused by the subscription will automatically cause components subscribed to the data to be notified of the change and re-render.
However, if the updates you wish to perform on the local data in response to the subscription are more complex than just updating the values of fields, like deleting or creating new records, or adding and removing items from a connection, you can provide an updater
function to requestSubscription
for full control over how to update the store:
import type {Environment} from 'react-relay';
import type {CommentCreateSubscribeData} from 'CommentCreateSubscription.graphql';
const {graphql, requestSubscription} = require('react-relay');
function commentCreateSubscribe(
environment: Environment,
feedbackID: string,
input: CommentCreateSubscribeData,
) {
return requestSubscription(environment, {
subscription: graphql`
subscription CommentCreateSubscription(
$input: CommentCreateSubscribeData!
) {
comment_create_subscribe(data: $input) {
feedback_comment_edge {
cursor
node {
body {
text
}
}
}
}
}
`,
variables: {input},
updater: store => {
const feedbackRecord = store.get(feedbackID);
// Get connection record
const connectionRecord = ConnectionHandler.getConnection(
feedbackRecord,
'CommentsComponent_comments_connection',
);
// Get the payload returned from the server
const payload = store.getRootField('comment_create_subscribe');
// Get the edge inside the payload
const serverEdge = payload.getLinkedRecord('feedback_comment_edge');
// Build edge for adding to the connection
const newEdge = ConnectionHandler.buildConnectionEdge(
store,
connectionRecord,
serverEdge,
);
// Add edge to the end of the connection
ConnectionHandler.insertEdgeAfter(connectionRecord, newEdge);
},
onCompleted: () => {} /* Subscription established */,
onError: error => {} /* Subscription errored */,
onNext: response => {} /* Subscription payload received */,
});
}
module.exports = {subscribe: commentCreateSubscribe};
Let's distill this example:
updater
takes astore
argument, which is an instance of aRecordSourceSelectorProxy
; this interface allows you to imperatively write and read data directly to and from the Relay store. This means that you have full control over how to update the store in response to the subscription payload: you can create entirely new records, or update or delete existing ones. The full API for reading and writing to the Relay store is available here: https://facebook.github.io/relay/docs/en/relay-store.html- In our specific example, we're adding a new comment to our local store when we receive a subscription payload notifying us that a new comment has been created. Specifically, we're adding a new item to a connection; for more details on the specifics of how that works, check out our Updating Connections section.
- Note that the subscription payload is a root field record that can be read from the
store
, specifically using thestore.getRootField
API. In our case, we're reading thecomment_create_subcribe
root field, which is a root field in the subscription response. - Note that any local data updates caused by the mutation
updater
will automatically cause components subscribed to the data to be notified of the change and re-render.
Requesting a subscription with Hooks​
You can also use hooks to subscribe to a subscription query.
import {graphql, useSubscription} from 'react-relay';
import {useMemo} from 'react';
const subscription = graphql`subscription ...`;
function MyFunctionalComponent({ id }) {
// IMPORTANT: your config should be memoized, or at least not re-computed
// every render. Otherwise, useSubscription will re-render too frequently.
const config = useMemo(() => { variables: { id }, subscription }, [id]);
useSubscription(config);
return <div>Move Fast</div>
}
This is only a thin wrapper around the requestSubscription
API. It's behavior:
- Subscribe when the component is mounted with the given config
- Unsubscribe when the component is unmounted
If you have the need to do something more complicated, such as imperatively requesting a subscription, please use the requestSubscription
API directly.
Configuring the Network Layer​
You will need to Configure your Network layer to handle subscriptions.
Usually GraphQL subscriptions are communicated over WebSockets, here's an example using graphql-ws:
import {
...
Network,
Observable
} from 'relay-runtime';
import { createClient } from 'graphql-ws';
const wsClient = createClient({
url:'ws://localhost:3000',
});
const subscribe = (operation, variables) => {
return Observable.create((sink) => {
return wsClient.subscribe(
{
operationName: operation.name,
query: operation.text,
variables,
},
sink,
);
});
}
const network = Network.create(fetchQuery, subscribe);
Alternatively, the legacy subscriptions-transport-ws library can be used too:
import {
...
Network,
Observable
} from 'relay-runtime';
import { SubscriptionClient } from 'subscriptions-transport-ws';
...
const subscriptionClient = new SubscriptionClient('ws://localhost:3000', {
reconnect: true,
});
const subscribe = (request, variables) => {
const subscribeObservable = subscriptionClient.request({
query: request.text,
operationName: request.name,
variables,
});
// Important: Convert subscriptions-transport-ws observable type to Relay's
return Observable.from(subscribeObservable);
};
const network = Network.create(fetchQuery, subscribe);
...
Is this page useful?
Help us make the site even better by answering a few quick questions.