Fix TimelinePanel re-renders on duplicate sync state events (#33268)

* Avoid TimelinePanel re-renders on duplicate sync state events

* A better solution avoiding to store the entire syncState
This commit is contained in:
rbondesson
2026-04-24 12:54:01 +00:00
committed by GitHub
parent 76b65b14de
commit 15699c557d
2 changed files with 51 additions and 8 deletions
@@ -24,7 +24,7 @@ import {
type MatrixClient,
type Relations,
type MatrixError,
type SyncState,
SyncState,
TimelineWindow,
Thread,
ThreadEvent,
@@ -192,9 +192,6 @@ interface IState {
backPaginating: boolean;
forwardPaginating: boolean;
// cache of matrixClient.getSyncState() (but from the 'sync' event)
clientSyncState: SyncState | null;
// should the event tiles have twelve hour times
isTwelveHour: boolean;
@@ -251,12 +248,17 @@ class TimelinePanel extends React.Component<IProps, IState> {
// A map of <callId, LegacyCallEventGrouper>
private callEventGroupers = new Map<string, LegacyCallEventGrouper>();
private initialReadMarkerId: string | null = null;
private syncImpliesForwardPaginating: boolean;
public constructor(props: IProps, context: React.ContextType<typeof RoomContext>) {
super(props, context);
debuglog("mounting");
this.syncImpliesForwardPaginating = TimelinePanel.isSyncForwardPaginating(
MatrixClientPeg.safeGet().getSyncState(),
);
// XXX: we could track RM per TimelineSet rather than per Room.
// but for now we just do it per room for simplicity.
if (this.props.manageReadMarkers) {
@@ -278,7 +280,6 @@ class TimelinePanel extends React.Component<IProps, IState> {
readMarkerEventId: this.initialReadMarkerId,
backPaginating: false,
forwardPaginating: false,
clientSyncState: MatrixClientPeg.safeGet().getSyncState(),
isTwelveHour: SettingsStore.getValue("showTwelveHourTimestamps"),
alwaysShowTimestamps: SettingsStore.getValue("alwaysShowTimestamps"),
readMarkerInViewThresholdMs: SettingsStore.getValue("readMarkerInViewThresholdMs"),
@@ -899,9 +900,17 @@ class TimelinePanel extends React.Component<IProps, IState> {
private onSync = (clientSyncState: SyncState, prevState: SyncState | null, data?: object): void => {
if (this.unmounted) return;
this.setState({ clientSyncState });
const nextSyncImpliesForwardPaginating = TimelinePanel.isSyncForwardPaginating(clientSyncState);
if (nextSyncImpliesForwardPaginating === this.syncImpliesForwardPaginating) return;
this.syncImpliesForwardPaginating = nextSyncImpliesForwardPaginating;
this.forceUpdate();
};
private static isSyncForwardPaginating(syncState: SyncState | null): boolean {
return syncState === SyncState.Prepared || syncState === SyncState.Catchup;
}
private readMarkerTimeout(readMarkerPosition: number | null): number {
return readMarkerPosition === 0
? (this.context?.readMarkerInViewThresholdMs ?? this.state.readMarkerInViewThresholdMs)
@@ -1832,8 +1841,7 @@ class TimelinePanel extends React.Component<IProps, IState> {
// If the state is PREPARED or CATCHUP, we're still waiting for the js-sdk to sync with
// the HS and fetch the latest events, so we are effectively forward paginating.
const forwardPaginating =
this.state.forwardPaginating || ["PREPARED", "CATCHUP"].includes(this.state.clientSyncState!);
const forwardPaginating = this.state.forwardPaginating || this.syncImpliesForwardPaginating;
const events = this.state.events;
return (
<MessagePanel