️(frontend) minimize observables consumed by useRaisedHandPosition

useRaisedHandPosition is called on every participant tile, so any
unnecessary subscription in it multiplies re-renders across the
whole conference.

Reduce the number of events subscribed to for remote participants
to the strict minimum, while keeping the subscription to the
relevant attributes of the local participant intact.
This commit is contained in:
lebaudantoine
2026-07-15 16:14:28 +02:00
parent 02088608c9
commit be033ae6c9
@@ -1,7 +1,9 @@
import type { Participant } from 'livekit-client'
import { Participant, RoomEvent } from 'livekit-client'
import {
useRoomContext,
useParticipantAttribute,
useParticipants,
useParticipantInfo,
useRemoteParticipants,
} from '@livekit/components-react'
import { isLocal } from '@/utils/livekit'
import { useMemo } from 'react'
@@ -12,28 +14,55 @@ type useRaisedHandProps = {
}
export function useRaisedHandPosition({ participant }: useRaisedHandProps) {
const { isHandRaised } = useRaisedHand({ participant })
const room = useRoomContext()
const { identity } = useParticipantInfo({ participant })
const localIdentity = room.localParticipant.identity
const participants = useParticipants()
const localHandRaisedAt = useParticipantAttribute('handRaisedAt', {
participant: room.localParticipant,
})
const remoteParticipants = useRemoteParticipants({
updateOnlyOn: [RoomEvent.ParticipantAttributesChanged],
})
const raisedHands = useMemo(() => {
const byIdentity = new Map<string, number>()
const add = (id: string, value?: string) => {
const time = new Date(value ?? '').getTime()
if (Number.isNaN(time)) return
const existing = byIdentity.get(id)
if (existing === undefined || time < existing) byIdentity.set(id, time)
}
if (localIdentity) add(localIdentity, localHandRaisedAt)
remoteParticipants.forEach((p) =>
add(p.identity, p.attributes.handRaisedAt)
)
return byIdentity
}, [remoteParticipants, localIdentity, localHandRaisedAt])
const sortedHands = useMemo(
() =>
[...raisedHands.entries()]
.map(([identity, time]) => ({ identity, time }))
.sort(
(a, b) => a.time - b.time || a.identity.localeCompare(b.identity)
),
[raisedHands]
)
const positionInQueue = useMemo(() => {
if (!isHandRaised) return
return (
participants
.filter((p) => !!p.attributes.handRaisedAt)
.sort((a, b) => {
const dateA = new Date(a.attributes.handRaisedAt)
const dateB = new Date(b.attributes.handRaisedAt)
return dateA.getTime() - dateB.getTime()
})
.findIndex((p) => p.identity === participant.identity) + 1
)
}, [participants, participant, isHandRaised])
const index = sortedHands.findIndex((h) => h.identity === identity)
return index === -1 ? undefined : index + 1
}, [sortedHands, identity])
return {
positionInQueue,
firstInQueue: positionInQueue == 1,
firstInQueue: positionInQueue === 1,
}
}