RocketChat/Rocket.Chat

View on GitHub
apps/meteor/client/components/RoomIcon/OmnichannelRoomIcon/provider/OmnichannelRoomIconProvider.tsx

Summary

Maintainability
A
30 mins
Test Coverage
import type { ReactNode } from 'react';
import React, { useCallback, useMemo } from 'react';
import { createPortal } from 'react-dom';
import { useSyncExternalStore } from 'use-sync-external-store/shim';

import type { AsyncState } from '../../../../lib/asyncState/AsyncState';
import { AsyncStatePhase } from '../../../../lib/asyncState/AsyncStatePhase';
import { OmnichannelRoomIconContext } from '../context/OmnichannelRoomIconContext';
import OmnichannelRoomIcon from '../lib/OmnichannelRoomIcon';

let icons = Array.from(OmnichannelRoomIcon.icons.values());

type OmnichannelRoomIconProviderProps = {
    children?: ReactNode;
};

export const OmnichannelRoomIconProvider = ({ children }: OmnichannelRoomIconProviderProps) => {
    const svgIcons = useSyncExternalStore(
        useCallback(
            (callback): (() => void) =>
                OmnichannelRoomIcon.on('change', () => {
                    icons = Array.from(OmnichannelRoomIcon.icons.values());
                    callback();
                }),
            [],
        ),
        (): string[] => icons,
    );

    return (
        <OmnichannelRoomIconContext.Provider
            value={useMemo(() => {
                const extractSnapshot = (app: string, iconName: string): AsyncState<string> => {
                    const icon = OmnichannelRoomIcon.get(app, iconName);

                    if (icon) {
                        return {
                            phase: AsyncStatePhase.RESOLVED,
                            value: icon,
                            error: undefined,
                        };
                    }

                    return {
                        phase: AsyncStatePhase.LOADING,
                        value: undefined,
                        error: undefined,
                    };
                };

                // We cache all the icons here, so that we can use them in the OmnichannelRoomIcon component
                const snapshots = new Map<string, AsyncState<string>>();

                return {
                    queryIcon: (
                        app: string,
                        iconName: string,
                    ): [subscribe: (onStoreChange: () => void) => () => void, getSnapshot: () => AsyncState<string>] => [
                        (callback): (() => void) =>
                            OmnichannelRoomIcon.on(`${app}-${iconName}`, () => {
                                snapshots.set(`${app}-${iconName}`, extractSnapshot(app, iconName));

                                // Then we call the callback (onStoreChange), signaling React to re-render
                                callback();
                            }),

                        // No problem here, because it's return value is a cached in the snapshots map on subsequent calls
                        (): AsyncState<string> => {
                            let snapshot = snapshots.get(`${app}-${iconName}`);

                            if (!snapshot) {
                                snapshot = extractSnapshot(app, iconName);
                                snapshots.set(`${app}-${iconName}`, snapshot);
                            }

                            return snapshot;
                        },
                    ],
                };
            }, [])}
        >
            {createPortal(
                <svg
                    xmlns='http://www.w3.org/2000/svg'
                    xmlnsXlink='http://www.w3.org/1999/xlink'
                    style={{ display: 'none' }}
                    dangerouslySetInnerHTML={{ __html: svgIcons.join('') }}
                />,
                document.body,
                'custom-icons',
            )}
            {children}
        </OmnichannelRoomIconContext.Provider>
    );
};