Skip to content

Commit 507236c

Browse files
authored
fix: sometimes loading older post does not work (#449)
1 parent 40401b7 commit 507236c

File tree

4 files changed

+58
-54
lines changed

4 files changed

+58
-54
lines changed

app/soapbox/actions/timelines.ts

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -155,7 +155,7 @@ const replaceHomeTimeline = (
155155
const expandTimeline = (timelineId: string, path: string, params: Partial<{ all: any[], none: any[], instance: string, any: any[], since_id: string, max_id: string, exclude_replies: boolean, with_muted: boolean, only_media: boolean, local: boolean, pinned: boolean, limit: number }>, done = noOp) =>
156156
(dispatch: AppDispatch, getState: () => RootState) => {
157157
const timeline = getState().timelines.get(timelineId) || {} as Record<string, any>;
158-
const isLoadingMore = !!params.max_id;
158+
const isLoadingOlder = !!params.max_id;
159159

160160
if (timeline.isLoading) {
161161
done();
@@ -166,17 +166,17 @@ const expandTimeline = (timelineId: string, path: string, params: Partial<{ all:
166166
params.since_id = timeline.getIn(['items', 0]);
167167
}
168168

169-
const isLoadingRecent = !!params.since_id;
169+
const isLoadingNewer = !!params.since_id;
170170

171-
dispatch(expandTimelineRequest(timelineId, isLoadingMore));
171+
dispatch(expandTimelineRequest(timelineId, isLoadingOlder));
172172

173173
return api(getState).get(path, { params }).then(response => {
174174
const next = getLinks(response).refs.find(link => link.rel === 'next');
175175
dispatch(importFetchedStatuses(response.data));
176-
dispatch(expandTimelineSuccess(timelineId, response.data, next ? next.uri : null, response.status === 206, isLoadingRecent, isLoadingMore));
176+
dispatch(expandTimelineSuccess(timelineId, response.data, next ? next.uri : null, response.status === 206, isLoadingNewer, isLoadingOlder));
177177
done();
178178
}).catch(error => {
179-
dispatch(expandTimelineFail(timelineId, error, isLoadingMore));
179+
dispatch(expandTimelineFail(timelineId, error, isLoadingOlder));
180180
done();
181181
});
182182
};
@@ -234,15 +234,15 @@ const expandTimelineRequest = (timeline: string, isLoadingMore: boolean) => ({
234234
skipLoading: !isLoadingMore,
235235
});
236236

237-
const expandTimelineSuccess = (timeline: string, statuses: APIEntity[], next: string | null, partial: boolean, isLoadingRecent: boolean, isLoadingMore: boolean) => ({
237+
const expandTimelineSuccess = (timeline: string, statuses: APIEntity[], next: string | null, partial: boolean, isLoadingNewer: boolean, isLoadingOlder: boolean) => ({
238238
type: TIMELINE_EXPAND_SUCCESS,
239239
timeline,
240240
statuses,
241241
next,
242242
partial,
243-
isLoadingRecent,
244-
skipLoading: !isLoadingMore,
245-
isLoadingMore,
243+
isLoadingNewer,
244+
skipLoading: !isLoadingOlder,
245+
isLoadingOlder,
246246
});
247247

248248
const expandTimelineFail = (timeline: string, error: AxiosError, isLoadingMore: boolean) => ({

app/soapbox/components/scroll-top-button.tsx

Lines changed: 15 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import throttle from 'lodash/throttle';
2-
import React, { useState, useEffect, useCallback } from 'react';
2+
import React, { useState, useEffect, useCallback, useMemo } from 'react';
33
import { useIntl, MessageDescriptor } from 'react-intl';
44

55
import Icon from 'soapbox/components/icon';
@@ -49,15 +49,19 @@ const ScrollTopButton: React.FC<IScrollTopButton> = ({
4949
}
5050
timer.current = null;
5151
}, 250);
52-
}, [autoload, autoloadThreshold, onClick, count]);
52+
}, [count, autoload, getScrollTop, autoloadThreshold, onClick]);
53+
54+
const handleScrollThrottled = useMemo(() => {
55+
return throttle(() => {
56+
if (getScrollTop() > threshold) {
57+
setScrolled(true);
58+
} else {
59+
setScrolled(false);
60+
}
61+
}, 150, { trailing: true });
62+
}, [getScrollTop, threshold]);
5363

54-
const handleScroll = useCallback(throttle(() => {
55-
if (getScrollTop() > threshold) {
56-
setScrolled(true);
57-
} else {
58-
setScrolled(false);
59-
}
60-
}, 150, { trailing: true }), [threshold]);
64+
const handleScroll = useCallback(() => handleScrollThrottled(), [handleScrollThrottled]);
6165

6266
const scrollUp = React.useCallback(() => {
6367
window.scrollTo({ top: 0 });
@@ -74,7 +78,7 @@ const ScrollTopButton: React.FC<IScrollTopButton> = ({
7478
return () => {
7579
window.removeEventListener('scroll', handleScroll);
7680
};
77-
}, [onClick]);
81+
}, [handleScroll, onClick]);
7882

7983
useEffect(() => {
8084
maybeUnload();
@@ -87,7 +91,7 @@ const ScrollTopButton: React.FC<IScrollTopButton> = ({
8791
return (
8892
<div className='left-1/2 -translate-x-1/2 fixed top-2 z-50'>
8993
<button
90-
className='flex items-center bg-primary-600 hover:bg-primary-700 hover:scale-105 active:scale-100 transition-transform text-white rounded-full px-4 py-2 space-x-1.5 cursor-pointer whitespace-nowrap'
94+
className='flex items-center bg-primary-600 hover:bg-primary-700 hover:scale-105 active:scale-100 transition-transform text-white rounded-full px-4 py-2 space-x-1.5 cursor-pointer whitespace-nowrap'
9195
onClick={handleClick}
9296
>
9397
<Icon src={require('@tabler/icons/arrow-bar-to-up.svg')} />

app/soapbox/features/ui/components/timeline.tsx

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { OrderedSet as ImmutableOrderedSet } from 'immutable';
22
import debounce from 'lodash/debounce';
3-
import React, { useCallback } from 'react';
3+
import React, { useCallback, useMemo } from 'react';
44
import { createPortal } from 'react-dom';
55
import { defineMessages } from 'react-intl';
66

@@ -19,14 +19,15 @@ interface ITimeline extends Omit<IStatusList, 'statusIds' | 'isLoading' | 'hasMo
1919
timelineId: string,
2020
}
2121

22+
const getStatusIds = makeGetStatusIds();
23+
2224
/** Scrollable list of statuses from a timeline in the Redux store. */
2325
const Timeline: React.FC<ITimeline> = ({
2426
timelineId,
2527
onLoadMore,
2628
...rest
2729
}) => {
2830
const dispatch = useAppDispatch();
29-
const getStatusIds = useCallback(makeGetStatusIds, [])();
3031

3132
const lastStatusId = useAppSelector(state => (state.timelines.get(timelineId)?.items || ImmutableOrderedSet()).last() as string | undefined);
3233
const statusIds = useAppSelector(state => getStatusIds(state, { type: timelineId }));
@@ -44,13 +45,21 @@ const Timeline: React.FC<ITimeline> = ({
4445
dispatch(dequeueTimeline(timelineId, onLoadMore));
4546
};
4647

47-
const handleScrollToTop = useCallback(debounce(() => {
48-
dispatch(scrollTopTimeline(timelineId, true));
49-
}, 100), [timelineId]);
48+
const handleScrollToTopDebounced = useMemo(() => {
49+
return debounce(() => {
50+
dispatch(scrollTopTimeline(timelineId, true));
51+
}, 100);
52+
}, [dispatch, timelineId]);
53+
54+
const handleScrollToTop = useCallback(() => handleScrollToTopDebounced(), [handleScrollToTopDebounced]);
55+
56+
const handleScrollDebounced = useMemo(() => {
57+
return debounce(() => {
58+
dispatch(scrollTopTimeline(timelineId, false));
59+
}, 100);
60+
}, [dispatch, timelineId]);
5061

51-
const handleScroll = useCallback(debounce(() => {
52-
dispatch(scrollTopTimeline(timelineId, false));
53-
}, 100), [timelineId]);
62+
const handleScroll = useCallback(() => handleScrollDebounced(), [handleScrollDebounced]);
5463

5564
return (
5665
<>

app/soapbox/reducers/timelines.ts

Lines changed: 17 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -89,46 +89,37 @@ const setFailed = (state: State, timelineId: string, failed: boolean) => {
8989
return state.update(timelineId, TimelineRecord(), timeline => timeline.set('loadingFailed', failed));
9090
};
9191

92-
const expandNormalizedTimeline = (state: State, timelineId: string, statuses: ImmutableList<ImmutableMap<string, any>>, next: string | null, isPartial: boolean, isLoadingRecent: boolean, isLoadingMore: boolean) => {
93-
let newIds = getStatusIds(statuses);
94-
let unseens = ImmutableOrderedSet<any>();
95-
92+
const expandNormalizedTimeline = (state: State, timelineId: string, retrievedStatus: ImmutableList<ImmutableMap<string, any>>, next: string | null, isPartial: boolean, isLoadingNewer: boolean, isLoadingOlder: boolean) => {
93+
let retrievedStatusIds = getStatusIds(retrievedStatus);
94+
let unpublishedStatusIds = ImmutableOrderedSet<any>();
9695
return state.withMutations((s) => {
9796
s.update(timelineId, TimelineRecord(), timeline => timeline.withMutations(timeline => {
9897
timeline.set('isLoading', false);
9998
timeline.set('loadingFailed', false);
10099
timeline.set('isPartial', isPartial);
101-
102-
if (!next && !isLoadingRecent) timeline.set('hasMore', false);
103-
100+
if (!next && !isLoadingNewer) timeline.set('hasMore', false);
104101
// Pinned timelines can be replaced entirely
105102
if (timelineId.endsWith(':pinned')) {
106-
timeline.set('items', newIds);
103+
timeline.set('items', retrievedStatusIds);
107104
return;
108105
}
106+
// if we arent loading older or newer posts and that we are not building the initial render
107+
if (!isLoadingNewer && !isLoadingOlder && timeline.items.count() > 0) {
108+
unpublishedStatusIds = retrievedStatusIds.subtract(timeline.items);
109+
retrievedStatusIds = retrievedStatusIds.subtract(unpublishedStatusIds);
110+
}
109111

110-
if (!newIds.isEmpty()) {
111-
// we need to sort between queue and actual list to avoid
112-
// messing with user position in the timeline by inserting inseen statuses
113-
unseens = ImmutableOrderedSet<any>();
114-
if (!isLoadingMore
115-
&& timeline.items.count() > 0
116-
&& newIds.first() > timeline.items.first()
117-
) {
118-
unseens = newIds.subtract(timeline.items);
119-
}
120-
121-
newIds = newIds.subtract(unseens);
122-
timeline.update('items', oldIds => {
123-
if (newIds.first() > oldIds.first()!) {
124-
return mergeStatusIds(oldIds, newIds);
112+
if (!retrievedStatusIds.isEmpty()) {
113+
timeline.update('items', statusInTimelineIds => {
114+
if (isLoadingOlder) {
115+
return mergeStatusIds(retrievedStatusIds, statusInTimelineIds);
125116
} else {
126-
return mergeStatusIds(newIds, oldIds);
117+
return mergeStatusIds(statusInTimelineIds, retrievedStatusIds);
127118
}
128119
});
129120
}
130121
}));
131-
unseens.forEach((statusId) => s.set(timelineId, updateTimelineQueue(s, timelineId, statusId).get(timelineId)));
122+
unpublishedStatusIds.forEach((statusId) => s.set(timelineId, updateTimelineQueue(s, timelineId, statusId).get(timelineId)));
132123
});
133124
};
134125

@@ -341,7 +332,7 @@ export default function timelines(state: State = initialState, action: AnyAction
341332
case TIMELINE_EXPAND_FAIL:
342333
return handleExpandFail(state, action.timeline);
343334
case TIMELINE_EXPAND_SUCCESS:
344-
return expandNormalizedTimeline(state, action.timeline, fromJS(action.statuses) as ImmutableList<ImmutableMap<string, any>>, action.next, action.partial, action.isLoadingRecent, action.isLoadingMore);
335+
return expandNormalizedTimeline(state, action.timeline, fromJS(action.statuses) as ImmutableList<ImmutableMap<string, any>>, action.next, action.partial, action.isLoadingNewer, action.isLoadingOlder);
345336
case TIMELINE_UPDATE:
346337
return updateTimeline(state, action.timeline, action.statusId);
347338
case TIMELINE_UPDATE_QUEUE:

0 commit comments

Comments
 (0)