website/versioned_docs/version-v13.0.0/guided-tour/updating-data/graphql-subscriptions.md
import DocsRating from '@site/src/core/DocsRating'; import {OssOnly, FbInternalOnly} from 'docusaurus-plugin-internaldocs-fb/internal';
<FbInternalOnly>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.
</FbInternalOnly> <OssOnly>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.
</OssOnly>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
}
}
}
Feedback object has been "liked" or "unliked". The feedback_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 updated like_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
}
}
}
`;
There are two ways of executing a subscription against the server. The requestSubscription API and using hooks.
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, the graphql tagged subscription, and the variables to use.input for the subscription can be Flow-typed with the autogenerated type available from the FeedbackLikeSubscription.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 an onCompleted and onError callbacks, which will respectively be called when the subscription is successfully established, or when an error occurs.requestSubscription also takes an onNext callback, which will be called whenever a subscription payload is received.Feedback object matching the given ID in the store, and update the values for the like_count field.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 a store argument, which is an instance of a RecordSourceSelectorProxy; 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.htmlstore, specifically using the store.getRootField API. In our case, we're reading the comment_create_subcribe root field, which is a root field in the subscription response.updater will automatically cause components subscribed to the data to be notified of the change and re-render.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:
If you have the need to do something more complicated, such as imperatively requesting a subscription, please use the requestSubscription API directly.
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);
...
At Facebook, the Network Layer has already been configured to handle GraphQL Subscriptions. For more details on writing subscriptions at Facebook, check out this guide. For a guide on setting up subscriptions on the server side, check out this wiki.
</FbInternalOnly> <DocsRating />