import React, { useEffect, useMemo, useState } from 'react'; import type { FlatList } from 'react-native-gesture-handler'; import { Channel, ChannelFilters, ChannelOptions, ChannelSort, Event, QueryChannelsRequestType, } from 'stream-chat'; import { ChannelListView } from './ChannelListView'; import { useChannelUpdated } from './hooks/listeners/useChannelUpdated'; import { useCreateChannelsContext } from './hooks/useCreateChannelsContext'; import { usePaginatedChannels } from './hooks/usePaginatedChannels'; import { ChannelsContextValue, ChannelsProvider, } from '../../contexts/channelsContext/ChannelsContext'; import { useChatContext } from '../../contexts/chatContext/ChatContext'; import { SwipeRegistryProvider } from '../../contexts/swipeableContext/SwipeRegistryContext'; import type { ChannelListEventListenerOptions } from '../../types/types'; export type ChannelListProps = Partial< Pick< ChannelsContextValue, | 'additionalFlatListProps' | 'setFlatListRef' | 'onSelect' | 'getChannelActionItems' | 'swipeActionsEnabled' | 'loadMoreThreshold' | 'maxUnreadCount' | 'numberOfSkeletons' | 'mutedStatusPosition' > > & { /** Optional function to filter channels prior to rendering the list. Do not use any complex logic that would delay the loading of the ChannelList. We recommend using a pure function with array methods like filter/sort/reduce. */ channelRenderFilterFn?: (channels: Array) => Array; /** * Object containing channel query filters * * @see See [Channel query documentation](https://getstream.io/chat/docs/query_channels) for a list of available filter fields * * @overrideType object * */ filters?: ChannelFilters; /** * If set to true, channels won't dynamically sort by most recent message, defaults to false */ lockChannelOrder?: boolean; /** * Function that overrides default behavior when a user gets added to a channel * * @param setChannels Setter for internal state property - `channels`. It's created from useState() hook. * @param event An [Event Object](https://getstream.io/chat/docs/event_object) corresponding to `notification.added_to_channel` event * @param filters Channel filters * @param sort Channel sort options * * @overrideType Function * */ onAddedToChannel?: ( setChannels: React.Dispatch>, event: Event, options?: ChannelListEventListenerOptions, ) => void; /** * Function that overrides default behavior when a channel gets deleted. In absence of this prop, the channel will be removed from the list. * * @param setChannels Setter for internal state property - `channels`. It's created from useState() hook. * @param event An [Event object](https://getstream.io/chat/docs/event_object) corresponding to `channel.deleted` event * * @overrideType Function * */ onChannelDeleted?: ( setChannels: React.Dispatch>, event: Event, ) => void; /** * Function that overrides default behavior when a channel gets hidden. In absence of this prop, the channel will be removed from the list. * * @param setChannels Setter for internal state property - `channels`. It's created from useState() hook. * @param event An [Event object](https://getstream.io/chat/docs/event_object) corresponding to `channel.hidden` event * * @overrideType Function * */ onChannelHidden?: ( setChannels: React.Dispatch>, event: Event, ) => void; /** * Function that overrides default behavior when a channel member.updated event is triggered * @param lockChannelOrder If set to true, channels won't dynamically sort by most recent message, defaults to false * @param setChannels Setter for internal state property - `channels`. It's created from useState() hook. * @param event An [Event object](https://getstream.io/chat/docs/event_object) corresponding to `member.updated` event * @param filters Channel filters * @param sort Channel sort options * @overrideType Function */ onChannelMemberUpdated?: ( lockChannelOrder: boolean, setChannels: React.Dispatch>, event: Event, options?: ChannelListEventListenerOptions, ) => void; /** * Function to customize behavior when a channel gets truncated * * @param setChannels Setter for internal state property - `channels`. It's created from useState() hook. * @param event [Event object](https://getstream.io/chat/docs/event_object) corresponding to `channel.truncated` event * * @overrideType Function * */ onChannelTruncated?: ( setChannels: React.Dispatch>, event: Event, ) => void; /** * Function that overrides default behavior when a channel gets updated * * @param setChannels Setter for internal state property - `channels`. It's created from useState() hook. * @param event An [Event object](https://getstream.io/chat/docs/event_object) corresponding to `channel.updated` event * * @overrideType Function * */ onChannelUpdated?: ( setChannels: React.Dispatch>, event: Event, ) => void; /** * Function that overrides default behavior when a channel gets visible. In absence of this prop, the channel will be added to the list. * * @param setChannels Setter for internal state property - `channels`. It's created from useState() hook. * @param event An [Event object](https://getstream.io/chat/docs/event_object) corresponding to `channel.visible` event * * @overrideType Function * */ onChannelVisible?: ( setChannels: React.Dispatch>, event: Event, ) => void; /** * Override the default listener/handler for event `message.new` * This event is received on channel, when a new message is added on a channel. * * @param lockChannelOrder If set to true, channels won't dynamically sort by most recent message, defaults to false * @param setChannels Setter for internal state property - `channels`. It's created from useState() hook. * @param event An [Event object](https://getstream.io/chat/docs/event_object) corresponding to `message.new` event * @param considerArchivedChannels If set to true, archived channels will be considered while updating the list of channels * @param filters Channel filters * @param sort Channel sort options * @overrideType Function * */ onNewMessage?: ( lockChannelOrder: boolean, setChannels: React.Dispatch>, event: Event, options?: ChannelListEventListenerOptions, ) => void; /** * Override the default listener/handler for event `notification.message_new` * This event is received on channel, which is not being watched. * * @param setChannels Setter for internal state property - `channels`. It's created from useState() hook. * @param event An [Event object](https://getstream.io/chat/docs/event_object) corresponding to `notification.message_new` event * @param filters Channel filters * @overrideType Function * */ onNewMessageNotification?: ( setChannels: React.Dispatch>, event: Event, options?: ChannelListEventListenerOptions, ) => void; /** * Function that overrides default behavior when a user gets removed from a channel * * @param setChannels Setter for internal state property - `channels`. It's created from useState() hook. * @param event An [Event object](https://getstream.io/chat/docs/event_object) corresponding to `notification.removed_from_channel` event * * @overrideType Function * */ onRemovedFromChannel?: ( setChannels: React.Dispatch>, event: Event, ) => void; /** * Object containing channel query options * @see See [Channel query documentation](https://getstream.io/chat/docs/query_channels) for a list of available option fields * */ options?: ChannelOptions; /** * Object containing channel sort parameters * @see See [Channel query documentation](https://getstream.io/chat/docs/query_channels) for a list of available sorting fields * */ sort?: ChannelSort; /** * A function that overrides the default ChannelManager queryChannels method, which is StreamChat.queryChannels. * It is particularly useful whenever we want to pass specific cids that we want to query but also want to * paginate over them (which is not possible through normal filters). It comes with with several rules/assumptions: * - StreamChat.queryChannels has to be called inside of queryChannelsOverride (as it updates important client state) * - The return type has to be Channel[] (which is the return type of StreamChat.queryChannels) */ queryChannelsOverride?: QueryChannelsRequestType; }; const DEFAULT_FILTERS = {}; const DEFAULT_OPTIONS = {}; const DEFAULT_SORT = {}; /** * This component fetches a list of channels, allowing you to select the channel you want to open. * The ChannelList renders a ChannelListView which provides the UI for the underlying React Native FlatList. * * @example ./ChannelList.md */ export const ChannelList = (props: ChannelListProps) => { const { additionalFlatListProps = {}, channelRenderFilterFn, filters = DEFAULT_FILTERS, // https://stackoverflow.com/a/60666252/10826415 loadMoreThreshold = 0.1, lockChannelOrder = false, maxUnreadCount = 255, numberOfSkeletons = 8, onAddedToChannel, onChannelDeleted, onChannelHidden, onChannelMemberUpdated, onChannelTruncated, onChannelUpdated, onChannelVisible, onNewMessage, onNewMessageNotification, onRemovedFromChannel, onSelect, options = DEFAULT_OPTIONS, getChannelActionItems, setFlatListRef, sort = DEFAULT_SORT, queryChannelsOverride, mutedStatusPosition = 'inlineTitle', swipeActionsEnabled = true, } = props; const [forceUpdate, setForceUpdate] = useState(0); const { client, enableOfflineSupport } = useChatContext(); const channelManager = useMemo(() => client.createChannelManager({}), [client]); /** * This hook sets the event handler overrides in the channelManager internally * whenever they change. We do this to avoid recreating the channelManager instance * every time these change, as we want to keep it as static as possible. * This protects us from something like defining the overrides as inline functions * causing the manager instance to be recreated over and over again. */ useEffect(() => { channelManager.setEventHandlerOverrides({ channelDeletedHandler: onChannelDeleted, channelHiddenHandler: onChannelHidden, channelTruncatedHandler: onChannelTruncated, channelVisibleHandler: onChannelVisible, memberUpdatedHandler: onChannelMemberUpdated ? (setChannels, event) => onChannelMemberUpdated(lockChannelOrder, setChannels, event, { filters, sort }) : undefined, newMessageHandler: onNewMessage ? (setChannels, event) => onNewMessage(lockChannelOrder, setChannels, event, { filters, sort }) : undefined, notificationAddedToChannelHandler: onAddedToChannel ? (setChannels, event) => onAddedToChannel(setChannels, event, { filters, sort }) : undefined, notificationNewMessageHandler: onNewMessageNotification ? (setChannels, event) => onNewMessageNotification(setChannels, event, { filters, sort }) : undefined, notificationRemovedFromChannelHandler: onRemovedFromChannel, }); }, [ channelManager, filters, lockChannelOrder, onAddedToChannel, onChannelDeleted, onChannelHidden, onChannelMemberUpdated, onChannelTruncated, onChannelVisible, onNewMessage, onNewMessageNotification, onRemovedFromChannel, sort, ]); useEffect(() => { if (queryChannelsOverride) { channelManager.setQueryChannelsRequest(queryChannelsOverride); } }, [channelManager, queryChannelsOverride]); useEffect(() => { channelManager.setOptions({ abortInFlightQuery: false, lockChannelOrder }); }, [channelManager, lockChannelOrder]); useEffect(() => { channelManager.registerSubscriptions(); return () => { channelManager.unregisterSubscriptions(); }; }, [channelManager]); const { channelListInitialized, channels, error, hasNextPage, loadingChannels, loadingNextPage, loadNextPage, refreshing, refreshList, reloadList, } = usePaginatedChannels({ channelManager, enableOfflineSupport, filters, options, setForceUpdate, sort, }); useChannelUpdated({ onChannelUpdated, setChannels: channelManager.setChannels, }); const channelsContext = useCreateChannelsContext({ additionalFlatListProps, channelListInitialized, channels: channelRenderFilterFn ? channelRenderFilterFn(channels ?? []) : channels, error, forceUpdate, hasNextPage, loadingChannels, loadingNextPage, loadMoreThreshold, loadNextPage, maxUnreadCount, numberOfSkeletons, onSelect, getChannelActionItems, swipeActionsEnabled, refreshing, refreshList, reloadList, setFlatListRef: (ref: FlatList | null) => { if (setFlatListRef) { setFlatListRef(ref); } }, mutedStatusPosition, }); return ( ); };