Skip to main content
Version: 3.xx.xx

Live Provider

Overview

refine lets you add Realtime support to your app via liveProvider prop for <Refine>. It can be used to update and show data in Realtime throughout your app. refine remains agnostic in its API to allow different solutions(Ably, Socket.IO, Mercure, supabase, Hasura, GraphQL Subscriptions, etc.) to be integrated.

A live provider must include following methods:

const liveProvider = {
subscribe: ({ channel, params: { ids }, types, callback }) => any,
unsubscribe: (subscription) => void,
publish?: (event) => void,
};
note

refine uses these methods in useSubscription and usePublish.

tip

refine includes out-of-the-box live providers to use in your projects like:

Usage

You must pass a live provider to the liveProvider prop of <Refine>.

App.tsx
import { Refine } from "@pankod/refine-core";

import liveProvider from "./liveProvider";

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

Creating a live provider

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

subscribe

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

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

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

const liveProvider = (client: Ably.Realtime): LiveProvider => {
return {
subscribe: ({ channel, types, params, callback }) => {
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);

return { channelInstance, listener };
},
};
};

Parameter Types

NameTypeDefault
channelstring
typesArray<"deleted" | "updated" | "created" | "*" | string>["*"]
params{ids?: string[]; [key: string]: any;}
callback(event: LiveEvent) => void;

LiveEvent

Return Type

Type
any
caution

The values returned from the subscribe method are passed to the unsubscribe method. Thus values needed for unsubscription must be returned from subscribe method.


refine will use this subscribe method in the useSubscription hook.

import { useSubscription } from "@pankod/refine-core";

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

Refer to the useSubscription documentation for more information.


unsubscribe

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

liveProvider.ts
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.

Parameter Types

NameTypeDescription
subscriptionanyThe values returned from the subscribe

Return Type

Type
void

publish

This method is used to publish an event on client side. Beware that publishing events on client side is not recommended and 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
const liveProvider = (client: Ably.Realtime): LiveProvider => {
return {
publish: (event: LiveEvent) => {
const channelInstance = client.channels.get(event.channel);

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

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

Parameter Types

NameType
eventLiveEvent

LiveEvent

Return Type

Type
void

refine will provide this publish method via the usePublish hook.

import { usePublish } from "@pankod/refine-core";

const publish = usePublish();

Refer to the usePublish documentation for more information.

liveMode

liveMode must be passed to <Refine> in options or supported hooks for liveProvider to work. If it's not provided live features won't be activated. Passing it to <Refine> in options configures it app wide and hooks will use this option. It can also be passed to hooks directly without passing to <Refine> for detailed configuration. If both are provided value passed to the hook will override the value at <Refine>.

Usage in <Refine>:

App.tsx
// ...

const App: React.FC = () => {
return (
<Refine liveProvider={liveProvider} options={{ liveMode: "auto" }} />
);
};

Usage in a hook:

const { data } = useList({ liveMode: "auto" });

auto

Queries of related resource are invalidated in Realtime as new events from subscription arrive. For example data from a useTable hook will be automatically updated when data is changed.

manual

Queries of related resource are not invalidated in Realtime, instead onLiveEvent is run with the event as new events from subscription arrive. For example while in an edit form, it would be undesirable for data shown to change. manual mode can be used to prevent data from changing.

off

Disables live mode. For example it can be used to disable some parts of the app if you have app wide live mode configuration in <Refine>.

onLiveEvent

Callback that is run when new events from subscription arrive. It can be passed to both <Refine> and supported hooks.

<Refine>

onLiveEvent passed to <Refine> will run every time when a new event occurs if liveMode is not off. It can be used for actions that are generally applicable to all events from active subscriptions.

App.tsx
// ...

const App: React.FC = () => {
return (
<Refine
liveProvider={liveProvider}
options={{ liveMode: "auto" }}
onLiveEvent={(event) => {
// Put your own logic based on event
}}
/>
);
};

Hooks

onLiveEvent passed to hooks runs when liveMode is not off. It is run with the event for related channel.

const { data } = useList({
liveMode: "manual",
onLiveEvent: (event) => {
// Put your own logic based on event
},
});

Supported Hooks

Supported data hooksSupported form hooksSupported other hooks
useList useForm useTable
useOne useModalForm useEditableTable
useMany useDrawerForm useSimpleList
useStepsForm useShow
useCheckboxGroup
useSelect
useRadioGroup

Supported Hooks Subscriptions

Supported hooks subscribe in the following way:

useList

useList({ resource: "posts" });
{
types: ["*"],
channel: "resources/posts"
}
tip

Following hooks uses useList under the hood and subscribe to same event.

useOne

useOne({ resource: "posts", id: "1" });
{
types: ["*"],
channel: "resources/posts",
params: { ids: ["1"] }
}
tip

Following hooks uses useOne under the hood and subscribe to same event.

useMany

useMany({ resource: "posts", ids: ["1", "2"] });
{
types: ["*"],
channel: "resources/posts"
params: { ids: ["1", "2"] }
}
tip

Following hooks uses useMany under the hood and subscribe to same event.

Publish Events from Hooks

refine publishes these events in the hooks. Let's see usage of hooks and what kind of events are published:

useCreate

const { mutate } = useCreate();

mutate({
resource: "posts",
values: {
title: "New Post",
},
});
Published event
{
channel: `resources/posts`,
type: "created",
payload: {
ids: ["id-of-created-post"]
},
date: new Date(),
}

useCreateMany

const { mutate } = useCreateMany();

mutate({
resource: "posts",
values: [
{
title: "New Post",
},
{
title: "Another New Post",
},
],
});
Published event
{
channel: `resources/posts`,
type: "created",
payload: {
ids: ["id-of-new-post", "id-of-another-new-post"]
},
date: new Date(),
}

useDelete

const { mutate } = useDelete();

mutate({
resource: "posts",
id: "1",
});
Published event
{
channel: `resources/posts`,
type: "deleted",
payload: {
ids: ["1"]
},
date: new Date(),
}

useDeleteMany

const { mutate } = useDeleteMany();

mutate({
resource: "posts",
ids: ["1", "2"],
});
Published event
{
channel: `resources/posts`,
type: "deleted",
payload: {
ids: ["1", "2"]
},
date: new Date(),
}

useUpdate

const { mutate } = useUpdate();

mutate({
resource: "posts",
id: "2",
values: { title: "New Post Title" },
});
Published event
{
channel: `resources/posts`,
type: "updated",
payload: {
ids: ["1"]
},
date: new Date(),
}

useUpdateMany

const { mutate } = useUpdateMany();

mutate({
resource: "posts",
ids: ["1", "2"],
values: { title: "New Post Title" },
});
Published event
{
channel: `resources/posts`,
type: "updated",
payload: {
ids: ["1", "2"]
},
date: new Date(),
}

Publish Events from API

Publishing in client side must be avoided generally. It's recommended to handle it in server side. Events published from the server must be in the following ways:

  • When creating a record:
{
channel: `resources/${resource}`,
type: "created",
payload: {
ids: [id]
},
date: new Date(),
}
  • When deleting a record:
{
channel: `resources/${resource}`,
type: "deleted",
payload: {
ids: [id]
},
date: new Date(),
}
  • When updating a record:
{
channel: `resources/${resource}`,
type: "updated",
payload: {
ids: [id]
},
date: new Date(),
}