diff --git a/src/app/components/read-receipt-avatars/ReadReceiptAvatars.tsx b/src/app/components/read-receipt-avatars/ReadReceiptAvatars.tsx index 2be195c63..14585d0c8 100644 --- a/src/app/components/read-receipt-avatars/ReadReceiptAvatars.tsx +++ b/src/app/components/read-receipt-avatars/ReadReceiptAvatars.tsx @@ -1,5 +1,5 @@ -import React, { useState } from 'react'; -import { Room } from 'matrix-js-sdk'; +import React, { useEffect, useState } from 'react'; +import { Room, RoomStateEvent, RoomStateEventHandlerMap } from 'matrix-js-sdk'; import { Icon, Icons, @@ -23,6 +23,7 @@ import { StackedAvatar } from '../stacked-avatar'; import { EventReaders } from '../event-readers'; import { stopPropagation } from '../../utils/keyboard'; import { useModalStyle } from '../../hooks/useModalStyle'; +import { useForceUpdate } from '../../hooks/useForceUpdate'; import * as css from './ReadReceiptAvatars.css'; const MAX_DISPLAY = 5; @@ -41,6 +42,28 @@ export function ReadReceiptAvatars({ const [open, setOpen] = useState(false); const [lotusTerminal] = useSetting(settingsAtom, 'lotusTerminal'); const modalStyle = useModalStyle(360); + const [, forceUpdate] = useForceUpdate(); + + // The avatars + names below are read from room member state at render time, so + // they only refresh when this component re-renders (which the timeline does on a + // receipt change). Re-render on a member-state change of any displayed reader so + // an avatar/display-name update shows live. RoomStateEvent.Members fires for name, + // avatar AND membership changes (RoomMemberEvent has no Avatar signal). + useEffect(() => { + const handleMembers: RoomStateEventHandlerMap[RoomStateEvent.Members] = ( + event, + _state, + member + ) => { + if (event.getRoomId() === room.roomId && userIds.includes(member.userId)) { + forceUpdate(); + } + }; + mx.on(RoomStateEvent.Members, handleMembers); + return () => { + mx.removeListener(RoomStateEvent.Members, handleMembers); + }; + }, [mx, room.roomId, userIds, forceUpdate]); if (userIds.length === 0) return null;