Skip to content

Latest commit

 

History

History
412 lines (347 loc) · 13.3 KB

File metadata and controls

412 lines (347 loc) · 13.3 KB
id title
real-time
Real Time

import realTimeDemo from '@site/static/img/guides-and-concepts/real-time/real-time.gif'; import manualMode from '@site/static/img/guides-and-concepts/real-time/manual-mode.gif'; import customSider from '@site/static/img/guides-and-concepts/real-time/custom-sider.gif';

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

Refer to the Live Provider documentation for detailed information. &#8594

We will be using Ably in this guide to provide real time features.

Installation

We need to install Ably live provider package from refine.

npm install @pankod/refine-ably

Setup

Since we will need apiKey from Ably, you must first register and get the key from Ably.

The app will have one resource: posts with CRUD pages(list, create, edit and show) similar to base example.

You can also refer to codesandbox to see final state of the app &#8594

Adding liveProvider

Firstly we create a ably client for @pankod/refine-ably live provider.

import { Ably } from "@pankod/refine-ably";

export const ablyClient = new Ably.Realtime("your-api-key");

Then pass liveProvider from @pankod/refine-ably to <Refine>.

// ...

//highlight-next-line
import { liveProvider } from "@pankod/refine-ably";

//highlight-next-line
import { ablyClient } from "./utility";
import { PostList, PostCreate, PostEdit, PostShow } from "pages/posts";

const App: React.FC = () => {
    return (
        <Refine
            routerProvider={routerProvider}
            dataProvider={dataProvider("https://api.fake-rest.refine.dev")}
            //highlight-start
            liveProvider={liveProvider(ablyClient)}
            liveMode="auto"
            //highlight-end
            resources={[
                {
                    name: "posts",
                    list: PostList,
                    create: PostCreate,
                    edit: PostEdit,
                    show: PostShow,
                    canDelete: true,
                },
            ]}
        />
    );
};

export default App;

:::note

For live features to work automatically we also added liveMode="auto".

Refer to the Live Provider documentation for detailed information. &#8594 :::


Real Time Demo

Configuring liveMode

We may not want to make real-time changes instantly in some cases. In these cases we can use manual mode to prevent the data changing instantly. Then we can handle the event manually.

For example in an edit page for a record, It would be better to handle real-time data manually to prevent synchronization problems caused by multiple editing sources. We would not want the data changing while we are trying to edit a record.

We will be alerting about changes in an alert box on top of the form instead of changing the data instantly.

// ...

export const PostEdit: React.FC = () => {
    //highlight-start
    const [deprecated, setDeprecated] =
        useState<"deleted" | "updated" | undefined>();
    //highlight-end

    const { formProps, saveButtonProps, queryResult } = useForm<IPost>({
        //highlight-start
        liveMode: "manual",
        onLiveEvent: (event) => {
            if (event.type === "deleted" || event.type === "updated") {
                setDeprecated(event.type);
            }
        },
        //highlight-end
    });

    //highlight-start
    const handleRefresh = () => {
        queryResult?.refetch();
        setDeprecated(undefined);
    };
    //highlight-end

    // ...

    return (
        <Edit /* ... */>
            //highlight-start
            {deprecated === "deleted" && (
                <Alert
                    message="This post is deleted."
                    type="warning"
                    style={{ marginBottom: 20 }}
                    action={<ListButton size="small" />}
                />
            )}
            //highlight-end 
            //highlight-start
            {deprecated === "updated" && (
                <Alert
                    message="This post is updated. Refresh to see changes."
                    type="warning"
                    style={{ marginBottom: 20 }}
                    action={
                        <RefreshButton size="small" onClick={handleRefresh} />
                    }
                />
            )}
            //highlight-end
            <Form {...formProps} layout="vertical">
                // ....
            </Form>
        </Edit>
    );
};

:::note

We can also implement similar thing in show page.

Refer to the codesandbox example for detailed information. &#8594 :::


Manual Mode Demo

Custom Subscriptions

You can subscribe to events emitted within refine in any place in your app with useSubscription.

For example, we can subscribe to create event for posts resource and we can show a badge for number of events in the sider menu.

Firstly, let's implement a custom sider like in this example.

Custom Sider Menu
import React, { useState } from "react";
import {
    AntdLayout,
    Menu,
    useMenu,
    useTitle,
    useNavigation,
    Grid,
    Icons,
} from "@pankod/refine";
import { antLayoutSider, antLayoutSiderMobile } from "./styles";

export const CustomSider: React.FC = () => {
    const [collapsed, setCollapsed] = useState<boolean>(false);
    const Title = useTitle();
    const { menuItems, selectedKey } = useMenu();
    const breakpoint = Grid.useBreakpoint();
    const { push } = useNavigation();

    const isMobile = !breakpoint.lg;

    return (
        <AntdLayout.Sider
            collapsible
            collapsedWidth={isMobile ? 0 : 80}
            collapsed={collapsed}
            breakpoint="lg"
            onCollapse={(collapsed: boolean): void => setCollapsed(collapsed)}
            style={isMobile ? antLayoutSiderMobile : antLayoutSider}
        >
            <Title collapsed={collapsed} />
            <Menu
                selectedKeys={[selectedKey]}
                mode="inline"
                onClick={({ key }) => {
                    if (!breakpoint.lg) {
                        setCollapsed(true);
                    }

                    push(key as string);
                }}
            >
                {menuItems.map(({ icon, label, route }) => {
                    const isSelected = route === selectedKey;
                    return (
                        <Menu.Item
                            style={{
                                fontWeight: isSelected ? "bold" : "normal",
                            }}
                            key={route}
                            icon={icon}
                        >
                            <div
                                style={{
                                    display: "flex",
                                    justifyContent: "space-between",
                                    alignItems: "center",
                                }}
                            >
                                {label}
                                {!collapsed && isSelected && (
                                    <Icons.RightOutlined />
                                )}
                            </div>
                        </Menu.Item>
                    );
                })}
            </Menu>
        </AntdLayout.Sider>
    );
};

Now, let's add a badge for number of create and update events for posts menu item.

import React, { useState } from "react";
import {
    AntdLayout,
    Menu,
    useMenu,
    useTitle,
    useNavigation,
    Grid,
    Icons,
    //highlight-start
    Badge,
    useSubscription,
    //highlight-end
} from "@pankod/refine";
import { antLayoutSider, antLayoutSiderMobile } from "./styles";

export const CustomSider: React.FC = () => {
    const [subscriptionCount, setSubscriptionCount] = useState(0);
    const [collapsed, setCollapsed] = useState<boolean>(false);
    const Title = useTitle();
    const { menuItems, selectedKey } = useMenu();
    const breakpoint = Grid.useBreakpoint();
    const { push } = useNavigation();

    const isMobile = !breakpoint.lg;

    //highlight-start
    useSubscription({
        channel: "resources/posts",
        type: ["created", "updated"],
        onLiveEvent: () => setSubscriptionCount((prev) => prev + 1),
    });
    //highlight-end

    return (
        <AntdLayout.Sider
            collapsible
            collapsedWidth={isMobile ? 0 : 80}
            collapsed={collapsed}
            breakpoint="lg"
            onCollapse={(collapsed: boolean): void => setCollapsed(collapsed)}
            style={isMobile ? antLayoutSiderMobile : antLayoutSider}
        >
            <Title collapsed={collapsed} />
            <Menu
                selectedKeys={[selectedKey]}
                mode="inline"
                onClick={({ key }) => {
                    if (!breakpoint.lg) {
                        setCollapsed(true);
                    }

                    //highlight-start
                    if (key === "/posts") {
                        setSubscriptionCount(0);
                    }
                    //highlight-end

                    push(key as string);
                }}
            >
                {menuItems.map(({ icon, label, route }) => {
                    const isSelected = route === selectedKey;
                    return (
                        <Menu.Item
                            style={{
                                fontWeight: isSelected ? "bold" : "normal",
                            }}
                            key={route}
                            icon={icon}
                        >
                            <div
                                style={{
                                    display: "flex",
                                    justifyContent: "space-between",
                                    alignItems: "center",
                                }}
                            >
                                //highlight-start
                                <div>
                                    {label}
                                    {label === "Posts" && (
                                        <Badge
                                            size="small"
                                            count={subscriptionCount}
                                            offset={[2, -15]}
                                        />
                                    )}
                                </div>
                                //highlight-end
                                {!collapsed && isSelected && (
                                    <Icons.RightOutlined />
                                )}
                            </div>
                        </Menu.Item>
                    );
                })}
            </Menu>
        </AntdLayout.Sider>
    );
};

:::tip

You can subscribe to specific ids with params. For example, you can subscribe to deleted and updated events from posts resource with id 1 and 2.

useSubscription({
    channel: "resources/posts",
    type: ["deleted", "updated"],
    //highlight-start
    params: {
        ids: ["1", "2"],
    },
    //highlight-end
    onLiveEvent: () => setSubscriptionCount((prev) => prev + 1),
});

:::


Custom Sider Demo

Live Condesandbox Example

<iframe src="https://codesandbox.io/embed/refine-ably-example-u9wg9?autoresize=1&fontsize=14&module=%2Fsrc%2FApp.tsx&theme=dark&view=preview" style={{width: "100%", height:"80vh", border: "0px", borderRadius: "8px", overflow:"hidden"}} title="refine-ably-example" allow="accelerometer; ambient-light-sensor; camera; encrypted-media; geolocation; gyroscope; hid; microphone; midi; payment; usb; vr; xr-spatial-tracking" sandbox="allow-forms allow-modals allow-popups allow-presentation allow-same-origin allow-scripts" ></iframe>