Notifications React - Next.js 13 (App router) implementation

I’m having problems trying to implement Notifications in my Next.js React application.

Problems:

  • Notifications sent from Sendbird Dashboard not being received, even though we are connected to sendbird
  • Sometimes when we receive a notification templateVariables are empty

I have created a NotificationContext, and a NotificationProvider is wrapped around the whole app. In that Context I’m connecting to sendbird and fetching the Feed channel and doing collection configuration:

    useEffect(() => {
        const authenticateFeed = async (userId: string, userToken: string) => {
            try {
                const user = await sendbird.connect(userId, userToken);
                setSendbirdUser(user);
            } catch (error) {
                console.log('Error authenticating user', error);
            }
            setIsLoading(false);
        };

        if (data?.user.id && data.token) {
            setIsLoading(true);
            authenticateFeed('3', MOCKED_USER_3_ACCESS_TOKEN);
        }
    }, [data]);

    useEffect(() => {
        const setupNotificationCollection = async () => {
            const filterKeyNumber = FILTER_CATEGORIES[state.filterKey];
            const filter = new MessageFilter();
            filter.customTypesFilter = [`${filterKeyNumber}`];

            const channel = await sendbird.feedChannel.getChannel(
                'notification_50002_feed',
            );
            const collection = channel.createNotificationCollection({ filter });
            collection.setMessageCollectionHandler(messageCollectionHandler);
            collection
                .initialize(
                    MessageCollectionInitPolicy.CACHE_AND_REPLACE_BY_API,
                )
                .onApiResult((err, messages) => {
                    if (messages) {
                        dispatch({
                            type: 'UPDATE_NOTIFICATIONS',
                            payload: messages,
                        });
                    }
                    if (err) console.log('Error', err);
                    setIsLoadingNotifications(false);
                });

            console.log('Notifications initialized.');
        };

        if (sendbirdUser) {
            setIsLoadingNotifications(true);
            setupNotificationCollection();
        }
        // eslint-disable-next-line
    }, [sendbirdUser, state.filterKey]);

Sendbird init and messageCollectionHandler is defined here:

export const sendbird = SendbirdChat.init({
    appId: process.env.NEXT_PUBLIC_SENDBIRD_LOCAL_APP_ID ?? '',
    modules: [new FeedChannelModule(), new GroupChannelModule()],
});

export const messageCollectionHandler: NotificationCollectionEventHandler = {
    onChannelUpdated: (context, channel) => {},
    onMessagesAdded: (context, channel, messages) => {
        console.log('messages: ', messages);
        const newMessages = messages.filter(
            (message) => message.messageStatus === 'SENT',
        );
        if (newMessages.length !== 0) {
            console.log('Messages added', newMessages);
            toast(<ToastNotification message={newMessages[0]} />);
        }
        sendbird.feedChannel.refreshNotificationCollections();
    },
    onMessagesUpdated: (context, channel, messages) => {
        sendbird.feedChannel.refreshNotificationCollections();
    },
    onMessagesDeleted: (context, channel, messageIds, messages) => {
        console.log('Messages deleted', messages);
    },
};

The only time that I seem to start getting real time notifications is when I dispatch a change to filterKey, which triggers the useEffect again. But I should be getting the notification before doing that as well…

Am I implementing it wrong? What is a right way to do that so that I can access the notifications through out the whole application if there is a user logged in, and receive real time notifications?

Thanks in advance.

Package versions:
@sendbird/chat”: “^4.10.4”,
“next”: “13.4.11”,
“next-auth”: “^4.22.3”,
“react”: “18.2.0”,

Hello @Lorena_Mrsic,

Welcome to the Sendbird Community.

I think it’s important to understand that the Sendbird Notifications product is not designed to be realtime. With Notifications, we’ve removed the websocket component to our connection schema and as such it’s designed primarily for use with Mobile platforms because the push notifications act as the “real time” delivery mechanism.

This is why you’re only seeing the new notifications when you’re changing the collection and refetching messages. If you’re looking for new notifications, you could utilize a polling mechanism to see if new notifications exist and then fetch when necessary.

@Tyler Thank you for your answer.

I see, do you know maybe why do I then get notifications in real time correctly after that?

I don’t need to change filterKey anymore to trigger the useEffect, it only needs to be triggered once after the first render, and then onMessageAdded gets triggered every time I send a new notification?

@Lorena_Mrsic Are you using the UIKit? If so, the UIKit is calling connect underneath which is using realtime connection, thus attributing to your MAU and PCC increases. I think it would be best to get a reproducible sample in something like stackblitz to get a better understanding of your implementation.

@Tyler No, I’m only using the Javascript sendbird/chat sdk.
App will implement UIKit for the Chat functionality in the future though…

I basically described all the code regarding Notifications configuration. Context (with the useEffects in previous code snippets) is wrapped around the app, and I’m using the context in my component:

export default function Notifications() {
    const { state, dispatch, isLoading, isLoadingNotifications } =
        useNotificationContext();

    const handleFilterChange = (key: TFilterCategorieKey) => {
        dispatch({ type: 'SET_FILTER_KEY', payload: key });
    };

    if (isLoading) return <Loading />;

    return (
        <div className="flex flex-col gap-8">
            <ButtonBack />
            <div className="flex w-full flex-col gap-4">
                <span className=" text-3xl text-opal">Notification center</span>
                <HorizontalLine />
                <div className="flex flex-row gap-3">
                    <Button onClick={() => handleFilterChange('All')}>
                        All
                    </Button>
                    <Button onClick={() => handleFilterChange('Offers')}>
                        Offers
                    </Button>
                </div>
                {isLoadingNotifications && <Loading />}
                {state.notifications.length === 0 && (
                    <div>There are no notifications.</div>
                )}
                {state.notifications.length > 0 &&
                    state.notifications.map((notification) => {
                        return (
                            <div key={notification.id}>
                                <NotificationMessageItem
                                    message={notification}
                                />
                            </div>
                        );
                    })}
            </div>
        </div>
    );
}

I currently don’t have time to create reproducible samples, but if I do I will let you know.

But that’s all of the code regarding Notifications implementation anyway, I issued this files on my report to Technical team on the same matter.

@Lorena_Mrsic,

The reason I asked is because there is no way for a real time update to have occurred if you’re only using the authenticateFeed as it’s only using API calls. There are no websocket events. So either the app is re-rendering causing the data to be refetched or connect is being called someone causing you to receive websocket events.

I would love to have a quick call with you and share my screen to show you whats happening, if that is somehow possible.

Unfortunately, I don’t have availability to jump on a call. That being said, I did relook over your code and I do believe I was slightly confused. I see you’re calling connect which is generating a websocket connection thus would explain why you’re seeing realtime updates after the first one.

One thing I need to know is when you’re sending the first notification, is it the first notification that user is ever receiving or the first notification the user is receiving after logging into the application.

Please note that connect() will cause your MAU and PCC to increase so please be aware of that trade off between using connect() and authenticateFeed().

It is the first notification that the user is receiving after logging into the application. The previous notifications aren’t shown in onMessageAdded function of the handler, only the ‘new ones’

If we only use authenticateFeed() will we still get the notifications inside collection handler? So in onMessageAdded?

@Lorena_Mrsic,

onMessageAdded only fires for new events. the onApiResult and onCacheResult is what loads the previous notifications. I see in your NotificationContext, you’re only building onApiResult.

I’ve attempted to reproduce this behavior where you state they’re not getting the first notification after signing in but I’m not able to produce a working reproduction.

One thing you could take a look at is after you’ve initialized the collection and and are connected to the websocket, if you inspect the websocket connection via the dev tools, and then send yourself a new notification you should be able to see whether you’re getting the message over the websocket or not.


In regards to authenticateFeed please note that my goal of pointing this out just to outline the implications of using connect() vs authenticateFeed() such as MAU and PCC.