Skip to content

Commit cff53b2

Browse files
mountinyOSBotify
authored andcommitted
Merge pull request #88321 from callstack-internal/VickyStash/bugfix/maintainVisibleContentPosition-fix
[CP Staging] Fix `maintainVisibleContentPosition` behaviour after migration to FlashList (cherry picked from commit 9b253b7) (cherry-picked to staging by AndrewGable)
1 parent 3dbd731 commit cff53b2

2 files changed

Lines changed: 18 additions & 7 deletions

File tree

src/components/FlashList/InvertedFlashList/index.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ type InvertedFlashListProps<T> = FlashListProps<T> & {
2020
};
2121

2222
function InvertedFlashList<T>({data, keyExtractor, initialScrollKey, onStartReached: onStartReachedProp, ...restProps}: InvertedFlashListProps<T>) {
23-
const {displayedData, onStartReached} = useFlashListScrollKey<T>({
23+
const {displayedData, onStartReached, maintainVisibleContentPosition} = useFlashListScrollKey<T>({
2424
data,
2525
keyExtractor,
2626
initialScrollKey,
@@ -36,6 +36,7 @@ function InvertedFlashList<T>({data, keyExtractor, initialScrollKey, onStartReac
3636
data={displayedData}
3737
keyExtractor={keyExtractor}
3838
CellRendererComponent={CellRendererComponent}
39+
maintainVisibleContentPosition={maintainVisibleContentPosition}
3940
/>
4041
);
4142
}

src/components/FlashList/useFlashListScrollKey.ts

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,26 +17,36 @@ type FlashListScrollKeyProps<T> = {
1717

1818
export default function useFlashListScrollKey<T>({data, keyExtractor, initialScrollKey, onStartReached}: FlashListScrollKeyProps<T>) {
1919
const [isInitialRender, setIsInitialRender] = useState(true);
20+
const [hasLinkingSettled, setHasLinkingSettled] = useState(!initialScrollKey);
2021

21-
// After the first render with sliced data, give FlashList one frame to lay out,
22-
// then switch to the full data array. maintainVisibleContentPosition keeps the target pinned.
22+
// Two-frame handoff for deep-link:
23+
// RAF 1: switch from sliced data to the full array — FlashList's default MVCP pins the
24+
// linked item through the data swap.
25+
// RAF 2: pinning has happened, disable MVCP so it doesn't cause later jumps.
2326
useEffect(() => {
2427
if (!isInitialRender || !initialScrollKey) {
2528
return;
2629
}
27-
requestAnimationFrame(() => setIsInitialRender(false));
30+
requestAnimationFrame(() => {
31+
setIsInitialRender(false);
32+
requestAnimationFrame(() => setHasLinkingSettled(true));
33+
});
2834
}, [isInitialRender, initialScrollKey]);
2935

36+
// `undefined` = leave FlashList's default (MVCP enabled) while we're still pinning the linked item.
37+
// `{disabled: true}` once that's done so MVCP can't interfere afterward.
38+
const maintainVisibleContentPosition: FlashListProps<T>['maintainVisibleContentPosition'] = hasLinkingSettled ? {disabled: true} : undefined;
39+
3040
if (!isInitialRender || !initialScrollKey) {
31-
return {displayedData: data, onStartReached};
41+
return {displayedData: data, onStartReached, maintainVisibleContentPosition};
3242
}
3343

3444
const targetIndex = data.findIndex((item, index) => keyExtractor(item, index) === initialScrollKey);
3545
if (targetIndex <= 0) {
36-
return {displayedData: data, onStartReached};
46+
return {displayedData: data, onStartReached, maintainVisibleContentPosition};
3747
}
3848

3949
// On the first render, slice from the target onward so the target item
4050
// appears at the visual bottom of the inverted list — no scrolling needed.
41-
return {displayedData: data.slice(targetIndex), onStartReached: () => {}};
51+
return {displayedData: data.slice(targetIndex), onStartReached: () => {}, maintainVisibleContentPosition};
4252
}

0 commit comments

Comments
 (0)