Skip to content

Commit 1f0409c

Browse files
authored
Merge pull request #8901 from daily-co/pre-901-improve-rendering-of-useparticipantproperty
PRE-901 Improve rendering of useParticipantProperty
2 parents 9a35408 + 793029c commit 1f0409c

File tree

5 files changed

+374
-386
lines changed

5 files changed

+374
-386
lines changed

src/DailyParticipants.tsx

Lines changed: 62 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import {
66
DailyParticipantsObject,
77
DailyWaitingParticipant,
88
} from '@daily-co/daily-js';
9-
import React, { useCallback, useEffect } from 'react';
9+
import React, { useCallback, useEffect, useState } from 'react';
1010
import {
1111
atom,
1212
atomFamily,
@@ -47,25 +47,28 @@ export const localIdState = atom<string>({
4747
default: '',
4848
});
4949

50-
export const participantsState = atom<ExtendedDailyParticipant[]>({
51-
key: RECOIL_PREFIX + 'participants-objects',
50+
export const participantIdsState = atom<string[]>({
51+
key: RECOIL_PREFIX + 'participant-ids',
5252
default: [],
5353
});
5454

55-
/**
56-
* Holds each individual participant's state object.
57-
*/
58-
export const participantState = selectorFamily<
55+
export const participantState = atomFamily<
5956
ExtendedDailyParticipant | null,
6057
string
6158
>({
62-
key: RECOIL_PREFIX + 'participant',
63-
get:
64-
(id) =>
65-
({ get }) => {
66-
const participants = get(participantsState);
67-
return participants.find((p) => p.session_id === id) ?? null;
68-
},
59+
key: RECOIL_PREFIX + 'participant-state',
60+
default: null,
61+
});
62+
63+
export const participantsState = selector<ExtendedDailyParticipant[]>({
64+
key: RECOIL_PREFIX + 'participants',
65+
get: ({ get }) => {
66+
const ids = get(participantIdsState);
67+
const participants = ids
68+
.map((id) => get(participantState(id)))
69+
.filter(Boolean) as ExtendedDailyParticipant[];
70+
return participants;
71+
},
6972
});
7073

7174
/**
@@ -76,8 +79,7 @@ export const participantPropertyState = selectorFamily<any, PropertyType>({
7679
get:
7780
({ id, properties }) =>
7881
({ get }) => {
79-
const participants = get(participantsState);
80-
const participant = participants.find((p) => p.session_id === id) ?? null;
82+
const participant = get(participantState(id));
8183

8284
return resolveParticipantPaths(participant, properties);
8385
},
@@ -123,13 +125,20 @@ export const DailyParticipants: React.FC<React.PropsWithChildren<{}>> = ({
123125
children,
124126
}) => {
125127
const daily = useDaily();
128+
const [initialized, setInitialized] = useState(false);
126129

127130
const initParticipants = useRecoilCallback(
128131
({ transact_UNSTABLE }) =>
129132
(participants: DailyParticipantsObject) => {
130133
transact_UNSTABLE(({ set }) => {
131134
set(localIdState, participants.local.session_id);
132-
set(participantsState, Object.values(participants));
135+
const participantsArray = Object.values(participants);
136+
const ids = participantsArray.map((p) => p.session_id);
137+
set(participantIdsState, ids);
138+
participantsArray.forEach((p) => {
139+
set(participantState(p.session_id), p);
140+
});
141+
setInitialized(true);
133142
});
134143
},
135144
[]
@@ -139,7 +148,7 @@ export const DailyParticipants: React.FC<React.PropsWithChildren<{}>> = ({
139148
* Retries every 100ms to initialize the state, until daily is ready.
140149
*/
141150
useEffect(() => {
142-
if (!daily) return;
151+
if (!daily || initialized) return;
143152
const interval = setInterval(() => {
144153
const participants = daily.participants();
145154
if (!('local' in participants)) return;
@@ -149,7 +158,7 @@ export const DailyParticipants: React.FC<React.PropsWithChildren<{}>> = ({
149158
return () => {
150159
clearInterval(interval);
151160
};
152-
}, [daily, initParticipants]);
161+
}, [daily, initialized, initParticipants]);
153162
const handleInitEvent = useCallback(() => {
154163
if (!daily) return;
155164
const participants = daily?.participants();
@@ -188,42 +197,37 @@ export const DailyParticipants: React.FC<React.PropsWithChildren<{}>> = ({
188197
| 'left-meeting'
189198
>[]
190199
) => {
191-
transact_UNSTABLE(({ reset, set }) => {
200+
transact_UNSTABLE(({ get, reset, set }) => {
192201
evts.forEach((ev) => {
193202
switch (ev.action) {
194203
case 'active-speaker-change': {
195204
const sessionId = ev.activeSpeaker.peerId;
196205
set(activeIdState, sessionId);
197-
set(participantsState, (prev) =>
198-
[...prev].map((p) =>
199-
p.session_id === sessionId
200-
? {
201-
...p,
202-
last_active: new Date(),
203-
}
204-
: p
205-
)
206-
);
206+
set(participantState(sessionId), (prev) => {
207+
if (!prev) return null;
208+
return {
209+
...prev,
210+
last_active: new Date(),
211+
};
212+
});
207213
break;
208214
}
209215
case 'participant-joined':
210-
set(participantsState, (prev) =>
211-
[...prev, ev.participant].filter(
212-
(participant, idx, arr) =>
213-
arr.findIndex(
214-
(p) => p.session_id === participant.session_id
215-
) == idx
216-
)
216+
set(participantIdsState, (prevIds) =>
217+
prevIds.includes(ev.participant.session_id)
218+
? prevIds
219+
: [...prevIds, ev.participant.session_id]
220+
);
221+
set(
222+
participantState(ev.participant.session_id),
223+
ev.participant
217224
);
218225
break;
219226
case 'participant-updated':
220-
set(participantsState, (prev) =>
221-
[...prev].map((p) =>
222-
p.session_id === ev.participant.session_id
223-
? { ...ev.participant, last_active: p.last_active }
224-
: p
225-
)
226-
);
227+
set(participantState(ev.participant.session_id), (prev) => ({
228+
...prev,
229+
...ev.participant,
230+
}));
227231
if (ev.participant.local) {
228232
set(localIdState, (prevId) =>
229233
prevId !== ev.participant.session_id
@@ -233,19 +237,27 @@ export const DailyParticipants: React.FC<React.PropsWithChildren<{}>> = ({
233237
}
234238
break;
235239
case 'participant-left':
236-
set(participantsState, (prev) =>
237-
[...prev].filter(
238-
(p) => ev.participant.session_id !== p.session_id
239-
)
240+
set(participantIdsState, (prevIds) =>
241+
prevIds.includes(ev.participant.session_id)
242+
? [
243+
...prevIds.filter(
244+
(id) => id !== ev.participant.session_id
245+
),
246+
]
247+
: prevIds
240248
);
249+
reset(participantState(ev.participant.session_id));
241250
break;
242251
/**
243252
* Reset stored participants, when meeting has ended.
244253
*/
245-
case 'left-meeting':
254+
case 'left-meeting': {
246255
reset(localIdState);
247-
reset(participantsState);
256+
const ids = get(participantIdsState);
257+
ids.forEach((id) => reset(participantState(id)));
258+
reset(participantIdsState);
248259
break;
260+
}
249261
}
250262
});
251263
});

src/hooks/useParticipantIds.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ export const useParticipantIds = (
6565
sort: defaultSort,
6666
}
6767
) => {
68+
// TODO: Optimize render performance for useParticipantIds
6869
const allParticipants = useRecoilValue(participantsState);
6970

7071
const filterFn = useMemo(() => {

test/.test-utils/event-emitter.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,3 +50,18 @@ export const emitParticipantJoined = (callObject: DailyCall, participant: Partia
5050
participant,
5151
});
5252
}
53+
54+
export const emitJoinedMeeting = (callObject: DailyCall, participants: Record<string, Partial<DailyParticipant>>) => {
55+
// @ts-ignore
56+
callObject.emit('joined-meeting', {
57+
action: 'joined-meeting',
58+
participants,
59+
});
60+
}
61+
62+
export const emitLeftMeeting = (callObject: DailyCall) => {
63+
// @ts-ignore
64+
callObject.emit('left-meeting', {
65+
action: 'left-meeting',
66+
});
67+
}

test/components/DailyAudio.test.tsx

Lines changed: 21 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { DailyAudio } from '../../src/components/DailyAudio';
99
import { DailyProvider } from '../../src/DailyProvider';
1010
import {
1111
emitActiveSpeakerChange,
12+
emitJoinedMeeting,
1213
emitParticipantLeft,
1314
emitParticipantUpdated,
1415
emitStartedCamera,
@@ -365,46 +366,44 @@ describe('DailyAudio', () => {
365366
faker.datatype.uuid(),
366367
faker.datatype.uuid(),
367368
];
368-
(callObject.participants as jest.Mock).mockImplementation(() => {
369-
const participants: Record<string, Partial<DailyParticipant>> = {
370-
local: mockParticipant({
371-
local: true,
372-
session_id: localSessionId,
373-
}),
374-
};
375-
remoteParticipants.forEach((id) => {
376-
const peer = mockParticipant({
377-
local: false,
378-
session_id: id,
379-
});
380-
peer.tracks.audio.state = 'playable';
381-
peer.tracks.audio.subscribed = true;
382-
participants[id] = peer;
369+
const participants: Record<string, Partial<DailyParticipant>> = {
370+
local: mockParticipant({
371+
local: true,
372+
session_id: localSessionId,
373+
}),
374+
};
375+
remoteParticipants.forEach((id) => {
376+
const peer = mockParticipant({
377+
local: false,
378+
session_id: id,
383379
});
384-
return participants;
380+
peer.tracks.audio.state = 'playable';
381+
peer.tracks.audio.subscribed = true;
382+
participants[id] = peer;
385383
});
384+
(callObject.participants as jest.Mock).mockImplementation(
385+
() => participants
386+
);
386387
const Wrapper = createWrapper(callObject);
387388
const { container } = render(
388389
<Wrapper>
389390
<DailyAudio maxSpeakers={3} />
390391
</Wrapper>
391392
);
392-
jest.useFakeTimers();
393+
act(() => emitJoinedMeeting(callObject, participants));
393394
act(() => emitStartedCamera(callObject));
394395
act(() => emitActiveSpeakerChange(callObject, remoteParticipants[0]));
395-
await waitFor(() =>
396-
expect(queryAudioById(remoteParticipants[0], container)).not.toBeNull()
397-
);
396+
await waitFor(() => {
397+
expect(queryAudioById(remoteParticipants[0], container)).not.toBeNull();
398+
});
398399
act(() => {
399-
jest.advanceTimersByTime(5000);
400400
emitActiveSpeakerChange(callObject, remoteParticipants[1]);
401401
});
402402
await waitFor(() => {
403403
expect(queryAudioById(remoteParticipants[0], container)).not.toBeNull();
404404
expect(queryAudioById(remoteParticipants[1], container)).not.toBeNull();
405405
});
406406
act(() => {
407-
jest.advanceTimersByTime(5000);
408407
emitActiveSpeakerChange(callObject, remoteParticipants[2]);
409408
});
410409
await waitFor(() => {
@@ -413,7 +412,6 @@ describe('DailyAudio', () => {
413412
expect(queryAudioById(remoteParticipants[2], container)).not.toBeNull();
414413
});
415414
act(() => {
416-
jest.advanceTimersByTime(5000);
417415
emitActiveSpeakerChange(callObject, remoteParticipants[3]);
418416
});
419417
await waitFor(() => {
@@ -422,7 +420,6 @@ describe('DailyAudio', () => {
422420
expect(queryAudioById(remoteParticipants[2], container)).not.toBeNull();
423421
expect(queryAudioById(remoteParticipants[3], container)).not.toBeNull();
424422
});
425-
jest.useRealTimers();
426423
});
427424
});
428425
});

0 commit comments

Comments
 (0)