From 92b0e07617b78840cef16091258c986a92ec05e6 Mon Sep 17 00:00:00 2001
From: DeviSriSaiCharan <143397375+DeviSriSaiCharan@users.noreply.github.com>
Date: Wed, 27 May 2026 19:50:59 +0530
Subject: [PATCH] fix: separate initial timeline loading from fetchMore loading
state (#20896)
Fixes: #20742
# Issue:
In the timeline activity inside the side panel, when we scroll down it
fetches more data and it displays a skeleton and after fetching finishes
the scroll position always jumps back to the top. Because of this, we
have to scroll to the bottom again to load more data.
https://github.com/user-attachments/assets/40d99df7-bdfb-4351-bc4f-baec2a035f13
## Root Cause
`useTimelineActivities` exposed a single `loading` state from
`useFindManyRecords`, which became true for both:
- Initial timeline fetch
- Pagination / fetchMore requests
`loadingTimelineActivities` becomes true whenever a network request is
triggered, including pagination requests where timeline records are
already available.
Because of this, the UI could not distinguish between the first query
loading state and subsequent fetchMore loading states.
## Fix
Added a separate firstQueryLoading state to detect only the first
timeline request.
The first query is identified by checking:
- the request is still loading
- and no timeline activities have been loaded yet
Once activities are already available, any future loading state is
treated as pagination/loading more records instead of initial loading.
This allows the UI to correctly handle:
- Skeleton loaders for first load
- Infinite scroll loaders for pagination
- Empty states after loading finishes
https://github.com/user-attachments/assets/48e8e078-82e2-43d8-823f-2f71e4f4f6e1
---
.../timeline-activities/components/TimelineCard.tsx | 12 ++++++++----
.../hooks/useTimelineActivities.ts | 11 ++++++++---
2 files changed, 16 insertions(+), 7 deletions(-)
diff --git a/packages/twenty-front/src/modules/activities/timeline-activities/components/TimelineCard.tsx b/packages/twenty-front/src/modules/activities/timeline-activities/components/TimelineCard.tsx
index d0b83e1088..00a6ff56b6 100644
--- a/packages/twenty-front/src/modules/activities/timeline-activities/components/TimelineCard.tsx
+++ b/packages/twenty-front/src/modules/activities/timeline-activities/components/TimelineCard.tsx
@@ -48,12 +48,16 @@ const StyledSidePanelPlaceholderWrapper = styled.div`
export const TimelineCard = () => {
const targetRecord = useTargetRecord();
const { isInSidePanel } = useLayoutRenderingContext();
- const { timelineActivities, loading, fetchMoreRecords } =
- useTimelineActivities(targetRecord);
+ const {
+ timelineActivities,
+ firstQueryLoading,
+ loadingMore,
+ fetchMoreRecords,
+ } = useTimelineActivities(targetRecord);
const isTimelineActivitiesEmpty = timelineActivities.length === 0;
- if (loading === true) {
+ if (firstQueryLoading === true) {
return ;
}
@@ -92,7 +96,7 @@ export const TimelineCard = () => {
events={timelineActivities ?? []}
/>
diff --git a/packages/twenty-front/src/modules/activities/timeline-activities/hooks/useTimelineActivities.ts b/packages/twenty-front/src/modules/activities/timeline-activities/hooks/useTimelineActivities.ts
index 30622f1427..39c25ebece 100644
--- a/packages/twenty-front/src/modules/activities/timeline-activities/hooks/useTimelineActivities.ts
+++ b/packages/twenty-front/src/modules/activities/timeline-activities/hooks/useTimelineActivities.ts
@@ -2,9 +2,9 @@ import { useLinkedObjectsTitle } from '@/activities/timeline-activities/hooks/us
import { type TimelineActivity } from '@/activities/timeline-activities/types/TimelineActivity';
import { type ActivityTargetableObject } from '@/activities/types/ActivityTargetableEntity';
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
-import { CoreObjectNameSingular } from 'twenty-shared/types';
import { useGenerateDepthRecordGqlFieldsFromObject } from '@/object-record/graphql/record-gql-fields/hooks/useGenerateDepthRecordGqlFieldsFromObject';
import { useFindManyRecords } from '@/object-record/hooks/useFindManyRecords';
+import { CoreObjectNameSingular } from 'twenty-shared/types';
import { capitalize, isDefined } from 'twenty-shared/utils';
// do we need to test this?
@@ -62,11 +62,16 @@ export const useTimelineActivities = (
useLinkedObjectsTitle(activityIds);
- const loading = loadingTimelineActivities;
+ const firstQueryLoading =
+ loadingTimelineActivities && timelineActivities.length === 0;
+
+ const loadingMore =
+ loadingTimelineActivities && timelineActivities.length > 0;
return {
timelineActivities,
- loading,
+ firstQueryLoading,
+ loadingMore,
fetchMoreRecords,
};
};