Skip to content

Commit 2c90780

Browse files
isouvezzclaude
andcommitted
fix(filmstrip): sync remoteParticipants between itemKey and ThumbnailWrapper
Filmstrip._gridItemKey and _listItemKey reference this.props._remoteParticipants (captured at the parent's render time), but ThumbnailWrapper._mapStateToProps independently reads state['features/filmstrip'].remoteParticipants from Redux. When a participant joins or leaves between the parent render and the child's selector call, the two arrays can diverge, causing a tile keyed for participant A to actually render participant B's video. Pass _remoteParticipants through react-window's itemData so that ThumbnailWrapper uses the same array snapshot that the itemKey functions use, eliminating the race condition. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 2c43785 commit 2c90780

2 files changed

Lines changed: 14 additions & 3 deletions

File tree

react/features/filmstrip/components/web/Filmstrip.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -996,6 +996,7 @@ class Filmstrip extends PureComponent <IProps, IState> {
996996
_filmstripWidth,
997997
_hasScroll,
998998
_isVerticalFilmstrip,
999+
_remoteParticipants,
9991000
_remoteParticipantsLength,
10001001
_resizableFilmstrip,
10011002
_rows,
@@ -1020,7 +1021,7 @@ class Filmstrip extends PureComponent <IProps, IState> {
10201021
height = { _filmstripHeight }
10211022
initialScrollLeft = { 0 }
10221023
initialScrollTop = { 0 }
1023-
itemData = {{ filmstripType }}
1024+
itemData = {{ filmstripType, remoteParticipants: _remoteParticipants }}
10241025
itemKey = { this._gridItemKey }
10251026
onItemsRendered = { this._onGridItemsRendered }
10261027
overscanRowCount = { 1 }
@@ -1039,6 +1040,7 @@ class Filmstrip extends PureComponent <IProps, IState> {
10391040
itemCount: _remoteParticipantsLength,
10401041
className: `filmstrip__videos remote-videos ${_resizableFilmstrip ? '' : 'height-transition'}`,
10411042
height: _filmstripHeight,
1043+
itemData: { remoteParticipants: _remoteParticipants },
10421044
itemKey: this._listItemKey,
10431045
itemSize: 0,
10441046
onItemsRendered: this._onListItemsRendered,

react/features/filmstrip/components/web/ThumbnailWrapper.tsx

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -136,7 +136,7 @@ class ThumbnailWrapper extends Component<IProps> {
136136
* @returns {IProps}
137137
*/
138138
function _mapStateToProps(state: IReduxState, ownProps: { columnIndex: number;
139-
data: { filmstripType: string; }; index?: number; rowIndex: number; }) {
139+
data: { filmstripType: string; remoteParticipants?: string[]; }; index?: number; rowIndex: number; }) {
140140
const _currentLayout = getCurrentLayout(state);
141141
const { remoteParticipants: remote } = state['features/filmstrip'];
142142
const activeParticipants = getActiveParticipantsIds(state);
@@ -145,7 +145,16 @@ function _mapStateToProps(state: IReduxState, ownProps: { columnIndex: number;
145145
const filmstripType = ownProps.data?.filmstripType;
146146
const stageFilmstrip = filmstripType === FILMSTRIP_TYPE.STAGE;
147147
const sortedActiveParticipants = activeParticipants.sort();
148-
const remoteParticipants = stageFilmstrip ? sortedActiveParticipants : remote;
148+
149+
// Use the remoteParticipants snapshot passed via itemData to ensure
150+
// consistency with Filmstrip's itemKey functions, which reference
151+
// the same array from the parent's render cycle. Reading directly
152+
// from Redux could yield a newer array if the store updated between
153+
// the parent render and this selector call, causing key/participant
154+
// mismatches (wrong video shown in a tile).
155+
const remoteParticipants = stageFilmstrip
156+
? sortedActiveParticipants
157+
: (ownProps.data?.remoteParticipants ?? remote);
149158
const remoteParticipantsLength = remoteParticipants.length;
150159
const localId = getLocalParticipant(state)?.id;
151160

0 commit comments

Comments
 (0)