import React, { PropsWithChildren, useContext, useEffect, useMemo, useState, useRef } from 'react'; import { Activity, GetFeedOptions, ReactionAPIResponse, StreamClient, ReactionAddOptions, UR, Reaction, FeedAPIResponse, ReactionAddChildOptions, ReactionFilterAPIResponse, ReactionFilterConditions, } from 'getstream'; import { FeedManager } from './FeedManager'; import { DefaultAT, DefaultUT, useStreamContext } from './StreamApp'; import isEqual from 'lodash/isEqual'; export type FeedContextValue< UT extends DefaultUT = DefaultUT, AT extends DefaultAT = DefaultAT, CT extends UR = UR, RT extends UR = UR, CRT extends UR = UR, PT extends UR = UR > = { feedGroup: string; feedManager: FeedManager; hasDoneRequest: boolean; hasNextPage: boolean; hasReverseNextPage: boolean; userId?: string; } & Pick< FeedManager, | 'loadNextPage' | 'loadNextReactions' | 'loadReverseNextPage' | 'onAddChildReaction' | 'onAddReaction' | 'onMarkAsRead' | 'onMarkAsSeen' | 'onRemoveActivity' | 'onRemoveChildReaction' | 'onRemoveReaction' | 'onToggleChildReaction' | 'onToggleReaction' | 'getActivityPath' | 'refresh' | 'refreshUnreadUnseen' > & Pick< FeedManager['state'], 'activities' | 'activityOrder' | 'realtimeAdds' | 'realtimeDeletes' | 'refreshing' | 'unread' | 'unseen' >; type DeleteRequestFn = (id: string) => Promise; export type FeedProps< UT extends DefaultUT = DefaultUT, AT extends DefaultAT = DefaultAT, CT extends UR = UR, RT extends UR = UR, CRT extends UR = UR, PT extends UR = UR > = { /** The feed group part of the feed */ feedGroup: string; /** The location that should be used for analytics when liking in the feed, * this is only useful when you have analytics enabled for your app. */ analyticsLocation?: string; /** Override activity delete request */ /* Components to display in the feed */ children?: React.ReactNode; doActivityDeleteRequest?: DeleteRequestFn; /** Override child reaction add request */ doChildReactionAddRequest?: ( kind: string, reaction: Reaction, data?: CRT, options?: ReactionAddChildOptions, ) => Promise>; /** Override child reaction delete request */ doChildReactionDeleteRequest?: DeleteRequestFn; /** The feed read handler (change only for advanced/complex use-cases) */ doFeedRequest?: ( client: StreamClient, feedGroup: string, userId?: string, options?: GetFeedOptions, ) => Promise>; /** Override reaction add request */ doReactionAddRequest?: ( kind: string, activity: Activity, data?: RT, options?: ReactionAddOptions, ) => Promise>; /** Override reaction delete request */ doReactionDeleteRequest?: DeleteRequestFn; /** Override reactions filter request */ doReactionsFilterRequest?: (options: ReactionFilterConditions) => Promise>; /** If true, feed shows the Notifier component when new activities are added */ notify?: boolean; /** Read options for the API client (eg. limit, ranking, ...) */ options?: GetFeedOptions; /** The user_id part of the feed */ userId?: string; }; export const FeedContext = React.createContext({}); export const FeedProvider = < UT extends DefaultUT = DefaultUT, AT extends DefaultAT = DefaultAT, CT extends UR = UR, RT extends UR = UR, CRT extends UR = UR, PT extends UR = UR >({ children, value, }: PropsWithChildren<{ value: FeedContextValue; }>) => {children}; export const useFeedContext = < UT extends DefaultUT = DefaultUT, AT extends DefaultAT = DefaultAT, CT extends UR = UR, RT extends UR = UR, CRT extends UR = UR, PT extends UR = UR >() => useContext(FeedContext) as FeedContextValue; export function Feed< UT extends DefaultUT = DefaultUT, AT extends DefaultAT = DefaultAT, CT extends UR = UR, RT extends UR = UR, CRT extends UR = UR, PT extends UR = UR >(props: FeedProps) { const { analyticsClient, client, user, errorHandler, sharedFeedManagers } = useStreamContext< UT, AT, CT, RT, CRT, PT >(); const { feedGroup, userId, children, options, notify } = props; const [, setForceUpdateState] = useState(0); const optionsReference = useRef(); if (!isEqual(optionsReference.current, options)) optionsReference.current = options; const feedId = client?.feed(feedGroup, userId).id; const manager = useMemo(() => { if (!feedId) return null; // TODO: check if any of the clients changed return ( sharedFeedManagers[feedId] || new FeedManager({ ...props, analyticsClient, client, user, errorHandler }) ); }, [feedId]); useEffect(() => { const forceUpdate = () => setForceUpdateState((prevState) => prevState + 1); if (manager) manager.props.notify = notify; manager?.register(forceUpdate); return () => manager?.unregister(forceUpdate); }, [manager, notify]); useEffect(() => { if (!manager) return; if (optionsReference.current) { manager.props.options = optionsReference.current; } manager.refresh(); }, [manager, optionsReference.current]); if (!manager) return null; const ctx = { feedGroup, userId, feedManager: manager, getActivityPath: manager.getActivityPath, onToggleReaction: manager.onToggleReaction, onAddReaction: manager.onAddReaction, onRemoveReaction: manager.onRemoveReaction, onToggleChildReaction: manager.onToggleChildReaction, onAddChildReaction: manager.onAddChildReaction, onRemoveChildReaction: manager.onRemoveChildReaction, onRemoveActivity: manager.onRemoveActivity, onMarkAsRead: manager.onMarkAsRead, onMarkAsSeen: manager.onMarkAsSeen, refresh: manager.refresh, refreshUnreadUnseen: manager.refreshUnreadUnseen, loadNextReactions: manager.loadNextReactions, loadNextPage: manager.loadNextPage, hasNextPage: manager.hasNextPage(), loadReverseNextPage: manager.loadReverseNextPage, hasReverseNextPage: manager.hasReverseNextPage(), activityOrder: manager.state.activityOrder, activities: manager.state.activities, realtimeAdds: manager.state.realtimeAdds, realtimeDeletes: manager.state.realtimeDeletes, refreshing: manager.state.refreshing, unread: manager.state.unread, unseen: manager.state.unseen, hasDoneRequest: manager.state.lastResponse != null, }; return {children}; }