useSubscribeToRecord

This Enterprise Edition hook is a specialized version of useSubscribe that subscribes to events concerning a single record.

Usage

The hook expects a callback function as its only argument, as it guesses the record and resource from the current context. The callback will be executed whenever an event is published on the resource/[resource]/[recordId] topic.

For instance, the following component displays a dialog when the record is updated by someone else:

import { useState } from 'react';
import { useEditContext, useFormContext } from 'react-admin';
import {
    Button,
    Dialog,
    DialogActions,
    DialogContent,
    DialogContentText,
    DialogTitle,
} from '@mui/material';
import { useSubscribeToRecord } from '@react-admin/ra-realtime';

const WarnWhenUpdatedBySomeoneElse = () => {
    const [open, setOpen] = useState(false);
    const [author, setAuthor] = useState<string | null>(null);
    const handleClose = () => {
        setOpen(false);
    };
    const { refetch } = useEditContext();
    const refresh = () => {
        refetch();
        handleClose();
    };
    const {
        formState: { isDirty },
    } = useFormContext();

    useSubscribeToRecord((event: Event) => {
        if (event.type === 'edited') {
            if (isDirty) {
                setOpen(true);
                setAuthor(event.payload.user);
            } else {
                refetch();
            }
        }
    });

    return (
        <Dialog
            open={open}
            onClose={handleClose}
            aria-labelledby="alert-dialog-title"
            aria-describedby="alert-dialog-description"
        >
            <DialogTitle id="alert-dialog-title">
                Post Updated by {author}
            </DialogTitle>
            <DialogContent>
                <DialogContentText id="alert-dialog-description">
                    Your changes and their changes may conflict. What do you
                    want to do?
                </DialogContentText>
            </DialogContent>
            <DialogActions>
                <Button onClick={handleClose}>Keep my changes</Button>
                <Button onClick={refresh}>
                    Get their changes (and lose mine)
                </Button>
            </DialogActions>
        </Dialog>
    );
};

const PostEdit = () => (
    <Edit>
        <SimpleForm>
            <TextInput source="id" disabled />
            <TextInput source="title" fullWidth />
            <TextInput source="body" fullWidth multiline />
            <WarnWhenUpdatedBySomeoneElse />
        </SimpleForm>
    </Edit>
);

useSubscribeToRecord reads the current resource and record from the ResourceContext and RecordContext respectively. In the example above, the notification is displayed when the app receives an event on the resource/books/123 topic.

Just like useSubscribe, useSubscribeToRecord unsubscribes from the topic when the component unmounts.

Tip: In the example above, <Show> creates the RecordContext- that’s why the useSubscribeToRecord hook is used in its child component instead of in the <BookShow> component.

You can provide the resource and record id explicitly if you are not in such contexts:

useSubscribeToRecord(event => { /* ... */ }, 'posts', 123);

Tip: If your reason to subscribe to events on a record is to keep the record up to date, you should use the useGetOneLive hook instead.

Parameters

Prop Required Type Default Description
callback Required function - The callback to execute when an event is received.
resource Optional string - The resource to subscribe to. Defaults to the resource in the ResourceContext.
recordId Optional string - The record id to subscribe to. Defaults to the id of the record in the RecordContext.
options Optional object - The subscription options.

callback

Whenever an event is published on the resource/[resource]/[recordId] topic, the function passed as the first argument will be called with the event as a parameter.

const [open, setOpen] = useState(false);
const [author, setAuthor] = useState<string | null>(null);
const { refetch } = useEditContext();
const {
    formState: { isDirty },
} = useFormContext();
useSubscribeToRecord((event: Event) => {
    if (event.type === 'edited') {
        if (isDirty) {
            setOpen(true);
            setAuthor(event.payload.user);
        } else {
            refetch();
        }
    }
});

Tip: Memoize the callback using useCallback to avoid unnecessary subscriptions/unsubscriptions.

const [open, setOpen] = useState(false);
const [author, setAuthor] = useState<string | null>(null);
const { refetch } = useEditContext();
const {
    formState: { isDirty },
} = useFormContext();

const handleEvent = useCallback(
    (event: Event) => {
        if (event.type === 'edited') {
            if (isDirty) {
                setOpen(true);
                setAuthor(event.payload.user);
            } else {
                refetch();
            }
        }
    },
    [isDirty, refetch, setOpen, setAuthor]
);

useSubscribeToRecord(handleEvent);

Just like for useSubscribe, the callback function receives an unsubscribe callback as its second argument. You can call it to unsubscribe from the topic after receiving a specific event.

useSubscribeToRecord((event: Event, unsubscribe) => {
    if (event.type === 'deleted') {
        // do something
        unsubscribe();
    }
    if (event.type === 'edited') {
        if (isDirty) {
            setOpen(true);
            setAuthor(event.payload.user);
        } else {
            refetch();
        }
    }
});

options

The options object can contain the following properties:

  • enabled: Whether to subscribe or not. Defaults to true
  • once: Whether to unsubscribe after the first event. Defaults to false.
  • unsubscribeOnUnmount: Whether to unsubscribe on unmount. Defaults to true.

See useSubscribe for more details.

recordId

The record id to subscribe to. By default, useSubscribeToRecord builds the topic it subscribes to using the id of the record in the RecordContext. But you can override this behavior by passing a record id as the third argument.

// will subscribe to the 'resource/posts/123' topic
useSubscribeToRecord(event => { /* ... */ }, 'posts', 123);

Note that if you pass a null record id, the hook will not subscribe to any topic.

resource

The resource to subscribe to. By default, useSubscribeToRecord builds the topic it subscribes to using the resource in the ResourceContext. But you can override this behavior by passing a resource name as the second argument.

// will subscribe to the 'resource/posts/123' topic
useSubscribeToRecord(event => { /* ... */ }, 'posts', 123);

Note that if you pass an empty string as the resource name, the hook will not subscribe to any topic.