useQuerySubscription
is a React hook that you can use to implement client-side updates of the page as soon as the content changes. It uses DatoCMS's Real-time Updates API to receive the updated query results in real-time, and is able to reconnect in case of network failures.
Live updates are great both to get instant previews of your content while editing it inside DatoCMS, or to offer real-time updates of content to your visitors (ie. news site).
- TypeScript ready;
- Compatible with vanilla React, Next.js and pretty much any other React-based solution;
- Installation
- Reference
- Initialization options
- Connection status
- Error object
- Example
- The
fetcher
option
npm install --save react-datocms
Import useQuerySubscription
from react-datocms
and use it inside your components like this:
const {
data: QueryResult | undefined,
error: ChannelErrorData | null,
status: ConnectionStatus,
} = useQuerySubscription(options: Options);
prop | type | required | description | default |
---|---|---|---|---|
enabled | boolean | ❌ | Whether the subscription has to be performed or not | true |
query | string | TypedDocumentNode |
✅ | The GraphQL query to subscribe | |
token | string | ✅ | DatoCMS API token to use | |
variables | Object | ❌ | GraphQL variables for the query | |
includeDrafts | boolean | ❌ | If true, draft records will be returned | |
excludeInvalid | boolean | ❌ | If true, invalid records will be filtered out | |
environment | string | ❌ | The name of the DatoCMS environment where to perform the query (defaults to primary environment) | |
contentLink | 'vercel-1' or undefined |
❌ | If true, embed metadata that enable Content Link | |
baseEditingUrl | string | ❌ | The base URL of the DatoCMS project | |
cacheTags | boolean | ❌ | If true, receive the Cache Tags associated with the query | |
initialData | Object | ❌ | The initial data to use on the first render | |
reconnectionPeriod | number | ❌ | In case of network errors, the period (in ms) to wait to reconnect | 1000 |
fetcher | a fetch-like function | ❌ | The fetch function to use to perform the registration query | window.fetch |
eventSourceClass | an EventSource-like class | ❌ | The EventSource class to use to open up the SSE connection | window.EventSource |
baseUrl | string | ❌ | The base URL to use to perform the query | https://graphql-listen.datocms.com |
The status
property represents the state of the server-sent events connection. It can be one of the following:
connecting
: the subscription channel is trying to connectconnected
: the channel is open, we're receiving live updatesclosed
: the channel has been permanently closed due to a fatal error (ie. an invalid query)
prop | type | description |
---|---|---|
code | string | The code of the error (ie. INVALID_QUERY ) |
message | string | An human friendly message explaining the error |
response | Object | The raw response returned by the endpoint, if available |
import React from 'react';
import { useQuerySubscription } from 'react-datocms';
const App: React.FC = () => {
const { status, error, data } = useQuerySubscription({
enabled: true,
query: `
query AppQuery($first: IntType) {
allBlogPosts {
slug
title
}
}`,
variables: { first: 10 },
token: 'YOUR_API_TOKEN',
});
const statusMessage = {
connecting: 'Connecting to DatoCMS...',
connected: 'Connected to DatoCMS, receiving live updates!',
closed: 'Connection closed',
};
return (
<div>
<p>Connection status: {statusMessage[status]}</p>
{error && (
<div>
<h1>Error: {error.code}</h1>
<div>{error.message}</div>
{error.response && (
<pre>{JSON.stringify(error.response, null, 2)}</pre>
)}
</div>
)}
{data && (
<ul>
{data.allBlogPosts.map((blogPost) => (
<li key={blogPost.slug}>{blogPost.title}</li>
))}
</ul>
)}
</div>
);
};
Be careful with how you define the fetcher
option: use a function that is
defined as a const
outside of the lexical scope where you're using
useQuerySubscription
.
If you don't, you could have an infinite render loop, because the function looks like new at every render of the component. For more info, see use-deep-compare-effect documentation.
The following example is ok:
const fetcher = (baseUrl, { headers, method, body }) => {
return fetch(baseUrl, {
headers: {
...headers,
'X-Custom-Header': "that's needed for some reason",
},
method,
body,
});
};
export default function Home() {
const { status, error, data } = useQuerySubscription({
fetcher,
// Other options here
});
return ...
}
This one is not, because the new function that is generated every time the component is rendered triggers another render:
export default function Home() {
const { status, error, data } = useQuerySubscription({
fetcher: (baseUrl, { headers, method, body }) => {
return fetch(baseUrl, {
headers: {
...headers,
'X-Custom-Header': "that's needed for some reason",
},
method,
body,
});
},
// Other options here
});
return ...
}