Skip to content

Commit 8161ba6

Browse files
committed
A better solution avoiding to store the entire syncState
1 parent a3820d6 commit 8161ba6

2 files changed

Lines changed: 26 additions & 16 deletions

File tree

apps/web/src/components/structures/TimelinePanel.tsx

Lines changed: 16 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ import {
2424
type MatrixClient,
2525
type Relations,
2626
type MatrixError,
27-
type SyncState,
27+
SyncState,
2828
TimelineWindow,
2929
Thread,
3030
ThreadEvent,
@@ -192,9 +192,6 @@ interface IState {
192192
backPaginating: boolean;
193193
forwardPaginating: boolean;
194194

195-
// cache of matrixClient.getSyncState() (but from the 'sync' event)
196-
clientSyncState: SyncState | null;
197-
198195
// should the event tiles have twelve hour times
199196
isTwelveHour: boolean;
200197

@@ -251,12 +248,17 @@ class TimelinePanel extends React.Component<IProps, IState> {
251248
// A map of <callId, LegacyCallEventGrouper>
252249
private callEventGroupers = new Map<string, LegacyCallEventGrouper>();
253250
private initialReadMarkerId: string | null = null;
251+
private syncImpliesForwardPaginating: boolean;
254252

255253
public constructor(props: IProps, context: React.ContextType<typeof RoomContext>) {
256254
super(props, context);
257255

258256
debuglog("mounting");
259257

258+
this.syncImpliesForwardPaginating = TimelinePanel.isSyncForwardPaginating(
259+
MatrixClientPeg.safeGet().getSyncState(),
260+
);
261+
260262
// XXX: we could track RM per TimelineSet rather than per Room.
261263
// but for now we just do it per room for simplicity.
262264
if (this.props.manageReadMarkers) {
@@ -278,7 +280,6 @@ class TimelinePanel extends React.Component<IProps, IState> {
278280
readMarkerEventId: this.initialReadMarkerId,
279281
backPaginating: false,
280282
forwardPaginating: false,
281-
clientSyncState: MatrixClientPeg.safeGet().getSyncState(),
282283
isTwelveHour: SettingsStore.getValue("showTwelveHourTimestamps"),
283284
alwaysShowTimestamps: SettingsStore.getValue("alwaysShowTimestamps"),
284285
readMarkerInViewThresholdMs: SettingsStore.getValue("readMarkerInViewThresholdMs"),
@@ -897,16 +898,19 @@ class TimelinePanel extends React.Component<IProps, IState> {
897898
this.forceUpdate();
898899
};
899900

900-
// The SDK intentionally emits duplicate Sync events such as SYNCING -> SYNCING
901-
// on each successful /sync so consumers can react to every sync loop. TimelinePanel
902-
// only uses this state to drive loading UI, so duplicate values do not change what
903-
// we render and would otherwise cause unnecessary MessagePanel re-renders.
904901
private onSync = (clientSyncState: SyncState, prevState: SyncState | null, data?: object): void => {
905902
if (this.unmounted) return;
906-
if (clientSyncState === this.state.clientSyncState) return;
907-
this.setState({ clientSyncState });
903+
const nextSyncImpliesForwardPaginating = TimelinePanel.isSyncForwardPaginating(clientSyncState);
904+
if (nextSyncImpliesForwardPaginating === this.syncImpliesForwardPaginating) return;
905+
906+
this.syncImpliesForwardPaginating = nextSyncImpliesForwardPaginating;
907+
this.forceUpdate();
908908
};
909909

910+
private static isSyncForwardPaginating(syncState: SyncState | null): boolean {
911+
return syncState === SyncState.Prepared || syncState === SyncState.Catchup;
912+
}
913+
910914
private readMarkerTimeout(readMarkerPosition: number | null): number {
911915
return readMarkerPosition === 0
912916
? (this.context?.readMarkerInViewThresholdMs ?? this.state.readMarkerInViewThresholdMs)
@@ -1837,8 +1841,7 @@ class TimelinePanel extends React.Component<IProps, IState> {
18371841

18381842
// If the state is PREPARED or CATCHUP, we're still waiting for the js-sdk to sync with
18391843
// the HS and fetch the latest events, so we are effectively forward paginating.
1840-
const forwardPaginating =
1841-
this.state.forwardPaginating || ["PREPARED", "CATCHUP"].includes(this.state.clientSyncState!);
1844+
const forwardPaginating = this.state.forwardPaginating || this.syncImpliesForwardPaginating;
18421845
const events = this.state.events;
18431846
return (
18441847
<MessagePanel

apps/web/test/unit-tests/components/structures/TimelinePanel-test.tsx

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -485,7 +485,7 @@ describe("TimelinePanel", () => {
485485
});
486486
});
487487

488-
it("does not re-render MessagePanel for duplicate sync state events", async () => {
488+
it("only re-renders when sync changes forward pagination state", async () => {
489489
const [client, room, events] = setupTestData();
490490
let timelinePanel: TimelinePanel | null = null;
491491

@@ -501,14 +501,21 @@ describe("TimelinePanel", () => {
501501
await flushPromises();
502502
await waitFor(() => expect(timelinePanel).toBeTruthy());
503503

504-
const setStateSpy = jest.spyOn(timelinePanel!, "setState");
504+
const forceUpdateSpy = jest.spyOn(timelinePanel!, "forceUpdate");
505505

506506
await act(async () => {
507507
client.emit(ClientEvent.Sync, SyncState.Syncing, SyncState.Syncing);
508508
await flushPromises();
509509
});
510510

511-
expect(setStateSpy).not.toHaveBeenCalled();
511+
expect(forceUpdateSpy).not.toHaveBeenCalled();
512+
513+
await act(async () => {
514+
client.emit(ClientEvent.Sync, SyncState.Prepared, SyncState.Syncing);
515+
await flushPromises();
516+
});
517+
518+
expect(forceUpdateSpy).toHaveBeenCalledTimes(1);
512519
});
513520

514521
describe("onRoomTimeline", () => {

0 commit comments

Comments
 (0)