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, 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,
};
refine uses these methods in useSubscription
and usePublish
.
refine includes out-of-the-box live providers to use in your projects like:
- Ably β Source Code - Demo
- Supabase β Source Code
- Appwrite β Source Code
Usageβ
You must pass a live provider to the liveProvider
prop of <Refine>
.
import { Refine } from "@pankod/refine";
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.
import { LiveProvider, LiveEvent } from "@pankod/refine";
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β
Name | Type | Default |
---|---|---|
channel | string | |
types | Array<"deleted" | "updated" | "created" | "* " | string > | ["*"] |
params | {ids?: string[]; [key: string]: any;} | |
callback | (event: LiveEvent) => void; |
Return Typeβ
Type |
---|
any |
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";
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.
const liveProvider = (client: Ably.Realtime): LiveProvider => {
return {
unsubscribe: (payload: {
channelInstance: Types.RealtimeChannelPromise;
listener: () => void;
}) => {
const { channelInstance, listener } = payload;
channelInstance.unsubscribe(listener);
},
};
};
If you don't handle unsubscription it could lead to memory leaks.
Parameter Typesβ
Name | Type | Description |
---|---|---|
subscription | any | The 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 notifyed. You can also publish your custom events using usePublish
.
const liveProvider = (client: Ably.Realtime): LiveProvider => {
return {
publish: (event: LiveEvent) => {
const channelInstance = client.channels.get(event.channel);
channelInstance.publish(event.type, event);
},
};
};
If publish
is used on client side you must handle the security of it by yourself.
Parameter Typesβ
Name | Type |
---|---|
event | LiveEvent |
Return Typeβ
Type |
---|
void |
refine will provide this publish method via the usePublish
hook.
import { usePublish } from "@pankod/refine";
const publish = usePublish();
Refer to the usePublish documentation for more information. β
liveMode
β
liveMode
must be passed to either <Refine>
or supported hooks for liveProvider
to work. If it's not provided live features won't be activated. Passing it to <Refine>
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>
:β
// ...
const App: React.FC = () => {
return <Refine liveProvider={liveProvider} 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.
// ...
const App: React.FC = () => {
return (
<Refine
liveProvider={liveProvider}
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 hooks | Supported form hooks | Supported 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"
}
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"] }
}
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"] }
}
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",
},
});
{
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",
},
],
});
{
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",
});
{
channel: `resources/posts`,
type: "deleted",
payload: {
ids: ["1"]
},
date: new Date(),
}
useDeleteMany
β
const { mutate } = useDeleteMany();
mutate({
resource: "posts",
ids: ["1", "2"],
});
{
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" },
});
{
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" },
});
{
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(),
}