Skip to content

feat(chat): live update of chat messages [AI]#230

Open
mbeernut wants to merge 1 commit intomainfrom
feat/chat-messages-live-update
Open

feat(chat): live update of chat messages [AI]#230
mbeernut wants to merge 1 commit intomainfrom
feat/chat-messages-live-update

Conversation

@mbeernut
Copy link
Contributor

  • connect ti signalR
  • auto update idle dialogue window
  • autoscroll bottom on update
  • fix chat list last message update


const chats = useMemo(() => data?.pages.flatMap((page) => page.data) ?? [], [data]);

useEffect(() => {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unconditional cache invalidation on mount: The useEffect with [userId, currentAccountId, queryClient] will invalidate the chats cache every time the ChatsProvider mounts (or when these deps change). This defeats the purpose of caching — every navigation to the chat list triggers a refetch. Consider whether a shorter staleTime or refetchOnWindowFocus/refetchOnMount on the query itself would be more appropriate and less heavy-handed.

const avatarId = message.identity.id;
const avatarPath = message.identity?.icon;
const avatarPath = message.identity.icon;
const senderName = message.sender?.identity?.name || message.identity.name;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This uses || which will fall through on any falsy value (e.g., empty string ""). Consider using ?? (nullish coalescing) instead for a more precise fallback:

const removeListener = addMessageListener((notification: ServerNotification) => {
if (notification.entity !== 'ChatMessage') return;

const message = notification.data as Message;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unsafe type assertion

chatId,
} = useMessages();

const shouldAutoScroll = (currentCount: number, previousCount: number): boolean => {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

shouldAutoScroll and updatePreviousMessageCount are not memoized: These are plain functions defined inside the component body. While they're lightweight, for consistency with the rest of the component (which uses useCallback), consider wrapping them — or even better, inline the logic since they're only used once each.

scrollToNewestMessage has messages.length in its dependency array:

ts
const scrollToNewestMessage = useCallback(() => {
if (messages.length === 0) return;
// ...
}, [messages.length, handleScrollToIndexFailed]);
This recreates the callback every time messages.length changes, which is the exact moment it's called from the useEffect. This works but the dependency is arguably unnecessary since the early return (messages.length === 0) is a safety check. You could remove messages.length from the deps and rely on the useEffect guard (shouldAutoScroll) to prevent calling it when there are no messages.

onScrollToIndexFailed={handleScrollToIndexFailed}
ListHeaderComponent={messagesFetchingNext ? <ActivityIndicator /> : null}
showsVerticalScrollIndicator={false}
maintainVisibleContentPosition={{
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This prop helps prevent the scroll position from jumping when new items are prepended. Removing it from an inverted FlatList could cause visual flickering when loading older messages (via handleLoadMore / infinite scroll). Was this intentional? If the custom scroll logic replaces this behavior, it should be documented.

extraData={messages}
inverted
keyExtractor={(item) => item.id}
keyExtractor={(item, index) => item.id || `message-${index}`}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using index as a fallback key in a list that supports pagination and live updates is risky — index-based keys can cause React to incorrectly reuse components when the data shifts. Under what circumstances would item.id be falsy? If it's from the SignalR notification, consider ensuring the ID is always present at the source.

revision: number;
chat: Chat;
sender: Sender;
chat?: Chat;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a significant type relaxation. It means every consumer of Message now needs to handle the case where chat and sender are undefined. Only ChatMessage.tsx was updated in this PR — are there other consumers of Message that could break at runtime? A search for usages of message.chat and message.sender across the codebase is recommended before merging.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants