Skip to main content
Version: 4.xx.xx

Realtime

Realtime data is an important part of modern applications. Seeing the changes in the details page, without refreshing the page not only improves the user experience but also increases the productivity of the users by preventing accidental updates.

Refine handles realtime data operations through Live Provider which provides a common interface for any integration. Once integrated, you'll get realtime updates across your app out of the box, without needing a further configuration.

Once a Live Provider is integrated, Refine takes care of the invalidation, refetching logic for your resources.

For example if a new record is created for products resource, a page where you use useList hook will automatically refetch the latest products data.

App.tsx
import { Refine, LiveProvider } from "@refinedev/core";

import { liveProvider } from "./my-live-provider";

const App = () => {
return (
<Refine
liveProvider={liveProvider}
options={{ liveMode: "auto" }}
onLiveEvent={(event) => {
console.log(event);
}}
>
{/* ... */}
</Refine>
);
};
my-page.tsx
import { useList } from "@refinedev/core";

const { data } = useList({
resource: "products",
// Can be configured per-hook basis.
liveMode: "auto", // manual or off
});

Supported Hooks

Refine hooks works out-of-the-box with Live Provider, means if the data these hooks consume is updated, they will automatically refetch.

See the Integrated Hooks section for more information.

Built-in Integrations

We have the following built-in integrations:

Live Provider

The Live Provider is an object that contains subscribe, unsubscribe and publish methods. These methods are utilized by Refine to subscribe, unsubscribe to a certain resource updates and publish updates.

A basic Live Provider looks like this:

live-provider.ts
import { LiveProvider } from "@refinedev/core";

export const liveProvider: LiveProvider = {
subscribe: async ({ callback, channel, types, meta, params }) => {
console.log(callback); // a function that will be called when there is an update
console.log(channel); // products, orders, etc.
console.log(types); // created, updated, deleted, "*", etc.
console.log(meta); // { fields: [], operation: "", queryContext: {}, variables: {} }

const { resource, id, ids } = params;

// subscribe to the resource updates
const unsubscribe = MyWSClient.subscribe({ channel });

// call the callback function when there is an update
MyWSClient.on("message", (data) => callback(data));

// return value will be passed to `unsubscribe` method.
return { unsubscribe };
},
unsubscribe: async ({ unsubscribe }) => {
// unsubscribe from the resource updates
unsubscribe();
},
publish: async ({ channel, type, payload, date }) => {
console.log(channel); // products, orders, etc.
console.log(type); // created, updated, deleted, etc.
console.log(payload); // { id: 1, name: "Product 1" }, { id: 2, name: "Product 2" }, etc.
console.log(date); // new Date()

// publish the data to the resource channel.
MyWSClient.publish({ channel, type, payload, date });
},
};

Hooks

While most of the features already works out-of-the-box with Live Provider, you can also use the following hooks to subscribe, unsubscribe and publish updates for your custom use cases.

useSubscription

The useSubscription hook can be used to subscribe to a certain resource updates. It calls Live Provider's subscribe method with the given parameters.

import { useSubscription } from "@refinedev/core";

useSubscription({
resource: "products",
types: ["created", "updated"],
onLiveEvent: (event) => {
console.log(event.channel); // products, orders, etc.
console.log(event.type); // created, updated, deleted, etc.
console.log(event.payload); // { id: 1, name: "Product 1" }, { id: 2, name: "Product 2" }, etc.
},
});

usePublish

While generally it's not recommended to publish updates from the frontend, you can use usePublish hook to publish updates to a certain resource. It calls Live Provider's publish method with the given parameters.

import { usePublish } from "@refinedev/core";

const publish = usePublish();

publish({
channel: "products",
type: "deleted",
payload: { id: 1 },
date: new Date(),
});

Creating a live provider with Ably

We will build the "Ably Live Provider" of @refinedev/ably from scratch to show the logic of how live provider methods interact with Ably.

Implementing subscribe method

This method is used to subscribe to a Realtime channel. Refine subscribes to the related channels using subscribe method in supported hooks to be aware of the data changes.

liveProvider.ts
import { LiveProvider, LiveEvent } from "@refinedev/core";
import Ably from "ably/promises";
import { Types } from "ably";

export const liveProvider = (client: Ably.Realtime): LiveProvider => {
return {
subscribe: ({ channel, types, params, callback, meta }) => {
console.log(channel); // products, orders, etc.
console.log(types); // created, updated, deleted, "*", etc.
console.log(params); // { id: 1 } or { ids: [1, 2, 3] }, etc.
console.log(callback); // a function that will be called when there is an update
console.log(meta); // { fields: [], operation: "", queryContext: {}, variables: {} }

const channelInstance = client.channels.get(channel);

const listener = function (message: MessageType) {
if (types.includes("*") || types.includes(message.data.type)) {
if (
message.data.type !== "created" &&
params?.ids !== undefined &&
message.data?.payload?.ids !== undefined
) {
if (
params.ids.filter((value) =>
message.data.payload.ids!.includes(value),
).length > 0
) {
callback(message.data as LiveEvent);
}
} else {
callback(message.data);
}
}
};

channelInstance.subscribe(listener);

// returned value will be passed to `unsubscribe` method.
// required for unsubscribing from the channel.
return { channelInstance, listener };
},
};
};

interface MessageType extends Types.Message {
data: LiveEvent;
}

Refine will use this subscribe method in the useSubscription hook.

import { useSubscription } from "@refinedev/core";

useSubscription({
channel: "channel-name",
onLiveEvent: (event) => {},
});

For more information, refer to the useSubscription documentation

Implementing unsubscribe method

This method is used to unsubscribe from a channel. The values returned from the subscribe method are passed to this method.

liveProvider.ts
export const liveProvider = (client: Ably.Realtime): LiveProvider => {
return {
unsubscribe: (payload: {
channelInstance: Types.RealtimeChannelPromise;
listener: () => void;
}) => {
const { channelInstance, listener } = payload;
channelInstance.unsubscribe(listener);
},
};
};
CAUTION

If you don't handle unsubscription, it could lead to memory leaks.

Implementing publish method

This method is used to publish an event on client side. Beware that publishing events on client side is not recommended and the best practice is to publish events from server side. You can refer Publish Events from API to see which events must be published from the server.

This publish is used in realated hooks. When publish is used, subscribers to these events are notified. You can also publish your custom events using usePublish.

liveProvider.ts
export const liveProvider = (client: Ably.Realtime): LiveProvider => {
return {
publish: ({ channel, type, payload, date, meta }: LiveEvent) => {
const channelInstance = client.channels.get(channel);

channelInstance.publish(type, event);
},
};
};
CAUTION

If publish is used on client side you must handle the security of it by yourself.

Refine will provide this publish method via the usePublish hook.

import { usePublish } from "@refinedev/core";

const publish = usePublish();

Usage

Now that we have created our live provider, we can use it in our application like below:

App.tsx
import { Refine } from "@refinedev/core";
import Ably from "ably/promises";

import { liveProvider } from "./liveProvider";

const ablyClient = new Ably.Realtime("your-ably-token");

const App = () => {
return <Refine liveProvider={liveProvider(ablyClient)}>{/*...*/}</Refine>;
};

Creating a live provider with GraphQL subscriptions

In this section, we will create a live provider for GraphQL subscriptions from scratch. We will use Hasura as an example, but the same logic can be applied to any GraphQL subscription provider.

@refinedev/hasura has a built-in live provider for Hasura subscriptions, but we will create our own from scratch to learn how it works.

Before diving into the code, let's see the difference between GraphQL queries and subscriptions.

GraphQL queries

query GetPosts {
posts {
id
title
content
}
}

GraphQL subscriptions

subscription GetPosts {
posts {
id
title
content
}
}

As you can see, the only difference between queries and subscriptions is the subscription keyword. This means that we can use the same logic for both queries and subscriptions. We already have a data provider for creating GraphQL queries, so we will use the same logic for GraphQL subscriptions.

Implementing subscribe method

When you call the useList, useOne or useMany hooks, they will call the subscribe method of the live provider.

Thus, we will be able to create subscription queries using the parameters of these hooks. After creating the subscription query, we will listen it using the graphql-ws client and return the unsubscribe method to use in the unsubscribe method of the live provider.

liveProvider.ts
import { LiveProvider } from "@refinedev/core";
import { Client } from "graphql-ws";

import {
generateUseListSubscription,
generateUseManySubscription,
generateUseOneSubscription,
} from "../utils";

const subscriptions = {
useList: generateUseListSubscription,
useOne: generateUseOneSubscription,
useMany: generateUseManySubscription,
};

export const liveProvider = (client: Client): LiveProvider => {
return {
subscribe: ({ callback, params, meta }) => {
const {
resource,
pagination,
sorters,
filters,
subscriptionType,
id,
ids,
} = params ?? {};

if (!meta) {
throw new Error(
"[useSubscription]: `meta` is required in `params` for graphql subscriptions",
);
}

if (!subscriptionType) {
throw new Error(
"[useSubscription]: `subscriptionType` is required in `params` for graphql subscriptions",
);
}

if (!resource) {
throw new Error(
"[useSubscription]: `resource` is required in `params` for graphql subscriptions",
);
}

const generateSubscription = subscriptions[subscriptionType];

const { query, variables, operation } = generateSubscription({
ids,
id,
resource,
filters,
meta,
pagination,
sorters,
});

const onNext = (payload: { data: any }) => {
callback(payload.data[operation]);
};

const unsubscribe = client.subscribe(
{
query,
variables,
},
{
next: onNext,
error: () => null,
complete: () => null,
},
);

// Will be passed to `unsubscribe` method.
return unsubscribe;
},
};
};
INFORMATION

generateUseListSubscription, generateUseOneSubscription and generateUseManySubscription are helper functions that generate subscription queries. They are same as the methods in the data provider of @refinedev/hasura. You can check them out here.

Refine hooks will create a subscription query using the parameters of the useSubscription hook and listen to it. When a live event is received, it will call the onLiveEvent method of the useSubscription hook.

import { useSubscription } from "@refinedev/core";

useSubscription({
channel: "posts",
enabled: true,
onLiveEvent: (event) => {
// called when a live event is received
console.log(event);
},
params: {
resource: "posts",
pagination: {
current: 1,
pageSize: 10,
},
subscriptionType: "useList",
},
meta: {
fields: [
"id",
"title",
{
category: ["title"],
},
"content",
"category_id",
"created_at",
],
},
});

Implementing unsubscribe method

We will call the unsubscribe method that we returned from the subscribe method to unsubscribe from the subscription query.

liveProvider.ts
import { LiveProvider } from "@refinedev/core";
import { Client } from "graphql-ws";

...

export const liveProvider = (client: Client): LiveProvider => {
return {
...
unsubscribe: (unsubscribe) => {
unsubscribe();
},
};
};

Usage

Now that we have created our live provider, we can use it in our application like below:

App.tsx
import { Refine } from "@refinedev/core";
import { createClient } from "graphql-ws";

import { liveProvider } from "./liveProvider";

const gqlWebSocketClient = createClient({
url: "YOUR_WS_URL",
});

const App: React.FC = () => {
return (
<Refine liveProvider={liveProvider(gqlWebSocketClient)}>
{/* ... */}{" "}
</Refine>
);
};