Skip to content

[No QA] Fix ManualSendMessage metrics measurements#82321

Open
cristipaval wants to merge 10 commits intomainfrom
fix-ManualSendMessage-metrics
Open

[No QA] Fix ManualSendMessage metrics measurements#82321
cristipaval wants to merge 10 commits intomainfrom
fix-ManualSendMessage-metrics

Conversation

@cristipaval
Copy link
Contributor

@cristipaval cristipaval commented Feb 12, 2026

The ManualSendMessage Sentry span was unreliable — it used a single global span ID, so any TextCommentFragment rendering (incoming messages, other pane re-renders) could end someone else's span. If the user scrolled up when sending, the message rendered off-screen, and the span could stay open for tens of seconds until something else accidentally closed it.

Explanation of Change

  • Each send-message span now includes the reportActionID in its ID (ManualSendMessage_), so only the fragment for that exact message can end it.
  • The span only starts when the chat is scrolled to the bottom. If you're scrolled up, we skip measurement entirely since we can't reliably detect when the message becomes visible.
  • Encapsulated ActionListContext value creation into the new useActionListContextValue hook, removing boilerplate from ReportScreen and SearchMoneyRequestReportPage. Added the scrollOffsetRef to it to help with the previous bullet.
  • Added cancelSpansByPrefix to clean up any orphaned send-message spans when navigating away from a report.

Fixed Issues

Part of #77176
PROPOSAL:

Tests

Tested that message sending works as expected.

  • Verify that no errors appear in the JS console

Offline tests

QA Steps

// TODO: These must be filled out, or the issue title must include "[No QA]."

  • Verify that no errors appear in the JS console

PR Author Checklist

  • I linked the correct issue in the ### Fixed Issues section above
  • I wrote clear testing steps that cover the changes made in this PR
    • I added steps for local testing in the Tests section
    • I added steps for the expected offline behavior in the Offline steps section
    • I added steps for Staging and/or Production testing in the QA steps section
    • I added steps to cover failure scenarios (i.e. verify an input displays the correct error message if the entered data is not correct)
    • I turned off my network connection and tested it while offline to ensure it matches the expected behavior (i.e. verify the default avatar icon is displayed if app is offline)
    • I tested this PR with a High Traffic account against the staging or production API to ensure there are no regressions (e.g. long loading states that impact usability).
  • I included screenshots or videos for tests on all platforms
  • I ran the tests on all platforms & verified they passed on:
    • Android: Native
    • Android: mWeb Chrome
    • iOS: Native
    • iOS: mWeb Safari
    • MacOS: Chrome / Safari
  • I verified there are no console errors (if there's a console error not related to the PR, report it or open an issue for it to be fixed)
  • I verified there are no new alerts related to the canBeMissing param for useOnyx
  • I followed proper code patterns (see Reviewing the code)
    • I verified that any callback methods that were added or modified are named for what the method does and never what callback they handle (i.e. toggleReport and not onIconClick)
    • I verified that comments were added to code that is not self explanatory
    • I verified that any new or modified comments were clear, correct English, and explained "why" the code was doing something instead of only explaining "what" the code was doing.
    • I verified any copy / text shown in the product is localized by adding it to src/languages/* files and using the translation method
      • If any non-english text was added/modified, I used JaimeGPT to get English > Spanish translation. I then posted it in #expensify-open-source and it was approved by an internal Expensify engineer. Link to Slack message:
    • I verified all numbers, amounts, dates and phone numbers shown in the product are using the localization methods
    • I verified any copy / text that was added to the app is grammatically correct in English. It adheres to proper capitalization guidelines (note: only the first word of header/labels should be capitalized), and is either coming verbatim from figma or has been approved by marketing (in order to get marketing approval, ask the Bug Zero team member to add the Waiting for copy label to the issue)
    • I verified proper file naming conventions were followed for any new files or renamed files. All non-platform specific files are named after what they export and are not named "index.js". All platform-specific files are named for the platform the code supports as outlined in the README.
    • I verified the JSDocs style guidelines (in STYLE.md) were followed
  • If a new code pattern is added I verified it was agreed to be used by multiple Expensify engineers
  • I followed the guidelines as stated in the Review Guidelines
  • I tested other components that can be impacted by my changes (i.e. if the PR modifies a shared library or component like Avatar, I verified the components using Avatar are working as expected)
  • I verified all code is DRY (the PR doesn't include any logic written more than once, with the exception of tests)
  • I verified any variables that can be defined as constants (ie. in CONST.ts or at the top of the file that uses the constant) are defined as such
  • I verified that if a function's arguments changed that all usages have also been updated correctly
  • If any new file was added I verified that:
    • The file has a description of what it does and/or why is needed at the top of the file if the code is not self explanatory
  • If a new CSS style is added I verified that:
    • A similar style doesn't already exist
    • The style can't be created with an existing StyleUtils function (i.e. StyleUtils.getBackgroundAndBorderStyle(theme.componentBG))
  • If new assets were added or existing ones were modified, I verified that:
    • The assets are optimized and compressed (for SVG files, run npm run compress-svg)
    • The assets load correctly across all supported platforms.
  • If the PR modifies code that runs when editing or sending messages, I tested and verified there is no unexpected behavior for all supported markdown - URLs, single line code, code blocks, quotes, headings, bold, strikethrough, and italic.
  • If the PR modifies a generic component, I tested and verified that those changes do not break usages of that component in the rest of the App (i.e. if a shared library or component like Avatar is modified, I verified that Avatar is working as expected in all cases)
  • If the PR modifies a component related to any of the existing Storybook stories, I tested and verified all stories for that component are still working as expected.
  • If the PR modifies a component or page that can be accessed by a direct deeplink, I verified that the code functions as expected when the deeplink is used - from a logged in and logged out account.
  • If the PR modifies the UI (e.g. new buttons, new UI components, changing the padding/spacing/sizing, moving components, etc) or modifies the form input styles:
    • I verified that all the inputs inside a form are aligned with each other.
    • I added Design label and/or tagged @Expensify/design so the design team can review the changes.
  • If a new page is added, I verified it's using the ScrollView component to make it scrollable when more elements are added to the page.
  • I added unit tests for any new feature or bug fix in this PR to help automatically prevent regressions in this user flow.
  • If the main branch was merged into this PR after a review, I tested again and verified the outcome was still expected according to the Test steps.

Screenshots/Videos

Android: Native
Android: mWeb Chrome
iOS: Native
iOS: mWeb Safari
MacOS: Chrome / Safari

@cristipaval cristipaval self-assigned this Feb 12, 2026
@cristipaval cristipaval requested review from a team as code owners February 12, 2026 20:49
@melvin-bot melvin-bot bot requested review from JmillsExpensify and MonilBhavsar and removed request for a team February 12, 2026 20:50
@melvin-bot
Copy link

melvin-bot bot commented Feb 12, 2026

@MonilBhavsar Please copy/paste the Reviewer Checklist from here into a new comment on this PR and complete it. If you have the K2 extension, you can simply click: [this button]

@melvin-bot melvin-bot bot removed the request for review from a team February 12, 2026 20:50
Copy link

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 87b4c9717a

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

});
onSubmit(newCommentTrimmed);
const reportActionID = rand64();
const isScrolledToBottom = !scrollPosition?.offset || scrollPosition.offset < CONST.REPORT.ACTIONS.ACTION_VISIBLE_THRESHOLD;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Use a real scroll offset before gating send-span start

The new isScrolledToBottom check depends on scrollPosition.offset, but this value is not wired to chat scroll events in this flow (repo-wide search shows setScrollPosition is only written in useReportScrollManager/index.native.ts, where it is reset to 0 during scrollToBottom). That makes the condition effectively always true (undefined/0), so spans are still started while the user is scrolled up and the off-screen orphan-span case this change targets still occurs.

Useful? React with 👍 / 👎.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

looking into it

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok, changed the approach for getting the current scroll position

Comment on lines +365 to +366
Performance.markStart(CONST.TIMING.SEND_MESSAGE, {message: newCommentTrimmed});
startSpan(`${CONST.TELEMETRY.SPAN_SEND_MESSAGE}_${reportActionID}`, {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Skip starting send spans for task-shortcut submissions

This starts a send-message span before calling onSubmit, but onSubmitComment can return early when handleCreateTask(text) succeeds in ReportFooter, so no comment with that reportActionID is created and TextCommentFragment never calls endSpan for it. In that task-creation path the span is only canceled later during screen cleanup, which produces canceled/orphan send-message telemetry samples.

Useful? React with 👍 / 👎.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that's fine, we only want to measure the text messages

@codecov
Copy link

codecov bot commented Feb 12, 2026

Codecov Report

❌ Looks like you've decreased code coverage for some files. Please write tests to increase, or at least maintain, the existing level of code coverage. See our documentation here for how to interpret this table.

Files with missing lines Coverage Δ
src/hooks/useActionListContextValue.ts 100.00% <100.00%> (ø)
src/pages/inbox/ReportScreen.tsx 66.58% <100.00%> (-0.08%) ⬇️
src/pages/inbox/ReportScreenContext.ts 100.00% <100.00%> (ø)
src/pages/inbox/report/ReportActionsList.tsx 75.89% <100.00%> (+0.14%) ⬆️
...pages/inbox/report/comment/TextCommentFragment.tsx 71.79% <100.00%> (+2.35%) ⬆️
src/libs/actions/Report/index.ts 61.46% <83.33%> (+0.10%) ⬆️
src/pages/inbox/report/ReportFooter.tsx 50.53% <0.00%> (ø)
src/libs/telemetry/activeSpans.ts 83.33% <50.00%> (-5.13%) ⬇️
src/pages/Search/SearchMoneyRequestReportPage.tsx 0.00% <0.00%> (ø)
...report/ReportActionCompose/ReportActionCompose.tsx 68.48% <14.28%> (-0.75%) ⬇️
... and 11 files with indirect coverage changes

const [childReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${linkedAction?.childReportID}`, {canBeMissing: true});

const [isBannerVisible, setIsBannerVisible] = useState(true);
const [scrollPosition, setScrollPosition] = useState<ScrollPosition>({});
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I moved the above state into a reusable hook to use on this page and in SearchMoneyRequestReportPage.tsx.

@mkhutornyi
Copy link
Contributor

I can help if this requires C+ review

@cristipaval
Copy link
Contributor Author

@codex review
@claude review

Copy link

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: b5988646ae

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

const isScrolledToBottom = scrollOffsetRef.current < CONST.REPORT.ACTIONS.ACTION_VISIBLE_THRESHOLD;
if (isScrolledToBottom) {
Performance.markStart(CONST.TIMING.SEND_MESSAGE, {message: newCommentTrimmed});
startSpan(`${CONST.TELEMETRY.SPAN_SEND_MESSAGE}_${reportActionID}`, {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Delay send span start until comment is actually posted

startSpan() is called before onSubmit(), but onSubmit can short-circuit without posting a comment (for example, ReportFooter.onSubmitComment() returns early when handleCreateTask(text) is true in src/pages/inbox/report/ReportFooter.tsx). In that flow no report action with this generated reportActionID is created, so TextCommentFragment never calls endSpan(...) for it; the span stays active until a later screen teardown and inflates send-message telemetry during long sessions.

Useful? React with 👍 / 👎.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we cleanup when the user moves away from the report page, so we won't have inflated spans

Copy link
Contributor

@JmillsExpensify JmillsExpensify left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No product review required on this one.

@roryabraham
Copy link
Contributor

@mkhutornyi I do think this one would benefit from a C+ review, thanks!

@roryabraham
Copy link
Contributor

@codex review

Copy link

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 9f5a0fd970

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

const reportActionID = rand64();

// The list is inverted, so an offset near 0 means the user is at the bottom (newest messages visible).
const isScrolledToBottom = scrollOffsetRef.current < CONST.REPORT.ACTIONS.ACTION_VISIBLE_THRESHOLD;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Avoid starting send spans with stale scroll offset

isScrolledToBottom depends on scrollOffsetRef.current, but that ref is only written in src/pages/inbox/report/ReportActionsList.tsx (I verified via rg "scrollOffsetRef.current ="), not in src/components/MoneyRequestReportView/MoneyRequestReportActionsList.tsx which is used when shouldDisplayMoneyRequestActionsList is true in ReportScreen. In those money-request reports the value remains its default 0, so this condition still starts ManualSendMessage_<actionID> spans even when the user is scrolled away from the latest message; since spans are now ended by exact reportActionID, off-screen sent messages can remain open until navigation cleanup and skew send-message telemetry.

Useful? React with 👍 / 👎.

const [scrollPosition, setScrollPosition] = useState<ScrollPosition>({});
const scrollOffsetRef = useRef(0);

return useMemo(() => ({flatListRef, scrollPosition, setScrollPosition, scrollOffsetRef}), [scrollPosition]);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This file is compiled by React Compiler, so we don't need useMemo here. Let's remove it since it's not needed

file,
isInSidePanel = false,
pregeneratedResponseParams,
reportActionID,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

question: Would an alternative here be to return the reportActionID for the newly created actions? that way it doesn't have to be passed in. NAB

}

function cancelSpansByPrefix(prefix: string) {
for (const [spanId] of activeSpans.entries()) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Per code style conventions:

Suggested change
for (const [spanId] of activeSpans.entries()) {
for (const [spanID] of activeSpans.entries()) {


function useActionListContextValue(): ActionListContextType {
const flatListRef = useRef<FlatList>(null);
const [scrollPosition, setScrollPosition] = useState<ScrollPosition>({});
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like we are misusing state here: scrollPosition is an observed property that can change very frequently. Calling setScrollPosition isn't intended to cause re-renders - the list itself should control its own scroll position.

Created a plan to fix it.


Refactor Scroll State Management

Problem Summary

Two inefficiencies exist in the scroll position tracking code:

  1. Unused state causing re-renders: [scrollPosition](src/hooks/useActionListContextValue.ts) is state but never read - only written via setScrollPosition. Every write triggers unnecessary re-renders.
  2. Duplicate scroll tracking: [ReportActionsList.tsx](src/pages/inbox/report/ReportActionsList.tsx) maintains TWO refs tracking the same scroll offset:
  • Local scrollingVerticalOffset (line 219)
  • Context scrollOffsetRef (line 176)
  • Both updated with identical values (lines 369-370)

Changes Required

1. Convert scrollPosition from state to ref

**File: src/hooks/useActionListContextValue.ts**

Change from:

const [scrollPosition, setScrollPosition] = useState<ScrollPosition>({});

To:

const scrollPositionRef = useRef<ScrollPosition>({});

Update return statement to remove setScrollPosition and use scrollPositionRef.

2. Update type definitions

**File: src/pages/inbox/ReportScreenContext.ts**

Change ActionListContextType:

type ActionListContextType = {
    flatListRef: FlatListRefType;
    scrollPositionRef: RefObject<ScrollPosition>;  // Changed from scrollPosition/setScrollPosition
    scrollOffsetRef: RefObject<number>;
};

Update default context value accordingly.

3. Update useReportScrollManager

**File: src/hooks/useReportScrollManager/index.native.ts**

Change from:

const {flatListRef, setScrollPosition} = useContext(ActionListContext);

To:

const {flatListRef, scrollPositionRef} = useContext(ActionListContext);

Update scrollToBottom callback (line 33) to use ref:

scrollPositionRef.current = {offset: 0};

4. Eliminate duplicate scroll tracking in ReportActionsList

**File: src/pages/inbox/report/ReportActionsList.tsx**

Remove local scrollingVerticalOffset ref declaration (line 219).

Replace all usages with scrollOffsetRef:

  • Line 298: Pass scrollOffsetRef.current to getUnreadMarkerIndex
  • Line 364: Pass scrollOffsetRef to useReportUnreadMessageScrollTracking
  • Line 369: Remove scrollingVerticalOffset.current = ... (keep only scrollOffsetRef.current = ...)
  • Line 380: Change condition to use scrollOffsetRef.current

Files Modified

Performance Benefits

  • Eliminates unnecessary re-renders when scroll position changes (no longer triggers state updates)
  • Reduces memory overhead by removing duplicate ref tracking
  • Improves code clarity with single source of truth for scroll offset

diff
diff --git a/src/hooks/useActionListContextValue.ts b/src/hooks/useActionListContextValue.ts
index 7f4c1d1a6b7..ba73ff804cc 100644
--- a/src/hooks/useActionListContextValue.ts
+++ b/src/hooks/useActionListContextValue.ts
@@ -1,13 +1,13 @@
-import {useMemo, useRef, useState} from 'react';
+import {useRef} from 'react';
 import type {FlatList} from 'react-native';
 import type {ActionListContextType, ScrollPosition} from '@pages/inbox/ReportScreenContext';
 
 function useActionListContextValue(): ActionListContextType {
     const flatListRef = useRef<FlatList>(null);
-    const [scrollPosition, setScrollPosition] = useState<ScrollPosition>({});
+    const scrollPositionRef = useRef<ScrollPosition>({});
     const scrollOffsetRef = useRef(0);
 
-    return useMemo(() => ({flatListRef, scrollPosition, setScrollPosition, scrollOffsetRef}), [scrollPosition]);
+    return {flatListRef, scrollPositionRef, scrollOffsetRef};
 }
 
 export default useActionListContextValue;
diff --git a/src/hooks/useReportScrollManager/index.native.ts b/src/hooks/useReportScrollManager/index.native.ts
index 39e5a1b66ee..180196dd0b3 100644
--- a/src/hooks/useReportScrollManager/index.native.ts
+++ b/src/hooks/useReportScrollManager/index.native.ts
@@ -1,44 +1,39 @@
-import {useCallback, useContext} from 'react';
+import {useContext} from 'react';
 // eslint-disable-next-line no-restricted-imports
 import type {ScrollView} from 'react-native';
 import {ActionListContext} from '@pages/inbox/ReportScreenContext';
 import type ReportScrollManagerData from './types';
 
 function useReportScrollManager(): ReportScrollManagerData {
-    const {flatListRef, setScrollPosition} = useContext(ActionListContext);
+    const {flatListRef, scrollPositionRef} = useContext(ActionListContext);
 
     /**
      * Scroll to the provided index.
      */
-    const scrollToIndex = useCallback(
-        (index: number) => {
-            if (!flatListRef?.current) {
-                return;
-            }
-
-            flatListRef.current.scrollToIndex({index});
-        },
-        [flatListRef],
-    );
+    const scrollToIndex = (index: number) => {
+        if (!flatListRef?.current) {
+            return;
+        }
+        flatListRef.current.scrollToIndex({index});
+    };
 
     /**
      * Scroll to the bottom of the inverted FlatList.
      * When FlatList is inverted it's "bottom" is really it's top
      */
-    const scrollToBottom = useCallback(() => {
+    const scrollToBottom = () => {
         if (!flatListRef?.current) {
             return;
         }
 
-        setScrollPosition({offset: 0});
-
+        scrollPositionRef.current = {offset: 0};
         flatListRef.current?.scrollToOffset({animated: false, offset: 0});
-    }, [flatListRef, setScrollPosition]);
+    };
 
     /**
      * Scroll to the end of the FlatList.
      */
-    const scrollToEnd = useCallback(() => {
+    const scrollToEnd = () => {
         if (!flatListRef?.current) {
             return;
         }
@@ -51,18 +46,14 @@ function useReportScrollManager(): ReportScrollManagerData {
         }
 
         flatListRef.current.scrollToEnd({animated: false});
-    }, [flatListRef]);
+    };
 
-    const scrollToOffset = useCallback(
-        (offset: number) => {
-            if (!flatListRef?.current) {
-                return;
-            }
-
-            flatListRef.current.scrollToOffset({offset, animated: false});
-        },
-        [flatListRef],
-    );
+    const scrollToOffset = (offset: number) => {
+        if (!flatListRef?.current) {
+            return;
+        }
+        flatListRef.current.scrollToOffset({offset, animated: false});
+    };
 
     return {ref: flatListRef, scrollToIndex, scrollToBottom, scrollToEnd, scrollToOffset};
 }
diff --git a/src/hooks/useReportScrollManager/index.ts b/src/hooks/useReportScrollManager/index.ts
index 30c383e62c2..6b888584887 100644
--- a/src/hooks/useReportScrollManager/index.ts
+++ b/src/hooks/useReportScrollManager/index.ts
@@ -1,4 +1,4 @@
-import {useCallback, useContext} from 'react';
+import {useContext} from 'react';
 import {ActionListContext} from '@pages/inbox/ReportScreenContext';
 import type ReportScrollManagerData from './types';
 
@@ -8,50 +8,44 @@ function useReportScrollManager(): ReportScrollManagerData {
     /**
      * Scroll to the provided index. On non-native implementations we do not want to scroll when we are scrolling because
      */
-    const scrollToIndex = useCallback(
-        (index: number, isEditing?: boolean) => {
-            if (!flatListRef?.current || isEditing) {
-                return;
-            }
+    const scrollToIndex = (index: number, isEditing?: boolean) => {
+        if (!flatListRef?.current || isEditing) {
+            return;
+        }
 
-            flatListRef.current.scrollToIndex({index, animated: true});
-        },
-        [flatListRef],
-    );
+        flatListRef.current.scrollToIndex({index, animated: true});
+    };
 
     /**
      * Scroll to the bottom of the inverted FlatList.
      * When FlatList is inverted it's "bottom" is really it's top
      */
-    const scrollToBottom = useCallback(() => {
+    const scrollToBottom = () => {
         if (!flatListRef?.current) {
             return;
         }
 
         flatListRef.current.scrollToOffset({animated: false, offset: 0});
-    }, [flatListRef]);
+    };
 
     /**
      * Scroll to the end of the FlatList.
      */
-    const scrollToEnd = useCallback(() => {
+    const scrollToEnd = () => {
         if (!flatListRef?.current) {
             return;
         }
 
         flatListRef.current.scrollToEnd({animated: false});
-    }, [flatListRef]);
-
-    const scrollToOffset = useCallback(
-        (offset: number) => {
-            if (!flatListRef?.current) {
-                return;
-            }
-
-            flatListRef.current.scrollToOffset({animated: true, offset});
-        },
-        [flatListRef],
-    );
+    };
+
+    const scrollToOffset = (offset: number) => {
+        if (!flatListRef?.current) {
+            return;
+        }
+
+        flatListRef.current.scrollToOffset({animated: true, offset});
+    };
 
     return {ref: flatListRef, scrollToIndex, scrollToBottom, scrollToEnd, scrollToOffset};
 }
diff --git a/src/pages/inbox/ReportScreenContext.ts b/src/pages/inbox/ReportScreenContext.ts
index ec56c097eee..50b1e3e707f 100644
--- a/src/pages/inbox/ReportScreenContext.ts
+++ b/src/pages/inbox/ReportScreenContext.ts
@@ -19,13 +19,12 @@ type ScrollPosition = {offset?: number};
 
 type ActionListContextType = {
     flatListRef: FlatListRefType;
-    scrollPosition: ScrollPosition | null;
-    setScrollPosition: (position: {offset: number}) => void;
+    scrollPositionRef: RefObject<ScrollPosition>;
     scrollOffsetRef: RefObject<number>;
 };
 type ReactionListContextType = RefObject<ReactionListRef | null> | null;
 
-const ActionListContext = createContext<ActionListContextType>({flatListRef: null, scrollPosition: null, setScrollPosition: () => {}, scrollOffsetRef: {current: 0}});
+const ActionListContext = createContext<ActionListContextType>({flatListRef: null, scrollPositionRef: {current: {}}, scrollOffsetRef: {current: 0}});
 const ReactionListContext = createContext<ReactionListContextType>(null);
 
 export {ActionListContext, ReactionListContext};
diff --git a/src/pages/inbox/report/ReportActionsList.tsx b/src/pages/inbox/report/ReportActionsList.tsx
index 2640683af9e..04016ee50fc 100644
--- a/src/pages/inbox/report/ReportActionsList.tsx
+++ b/src/pages/inbox/report/ReportActionsList.tsx
@@ -216,7 +216,6 @@ function ReportActionsList({
         return unsubscribe;
     }, []);
 
-    const scrollingVerticalOffset = useRef(0);
     const readActionSkipped = useRef(false);
     const hasHeaderRendered = useRef(false);
     const linkedReportActionID = route?.params?.reportActionID;
@@ -295,7 +294,7 @@ function ReportActionsList({
                     currentUserAccountID,
                     prevSortedVisibleReportActionsObjects,
                     unreadMarkerTime,
-                    scrollingVerticalOffset: scrollingVerticalOffset.current,
+                    scrollingVerticalOffset: scrollOffsetRef.current,
                     prevUnreadMarkerReportActionID: prevUnreadMarkerReportActionID.current,
                 });
             if (shouldDisplayNewMarker) {
@@ -361,12 +360,11 @@ function ReportActionsList({
 
     const {isFloatingMessageCounterVisible, setIsFloatingMessageCounterVisible, trackVerticalScrolling, onViewableItemsChanged} = useReportUnreadMessageScrollTracking({
         reportID: report.reportID,
-        currentVerticalScrollingOffsetRef: scrollingVerticalOffset,
+        currentVerticalScrollingOffsetRef: scrollOffsetRef,
         readActionSkippedRef: readActionSkipped,
         unreadMarkerReportActionIndex,
         isInverted: true,
         onTrackScrolling: (event: NativeSyntheticEvent<NativeScrollEvent>) => {
-            scrollingVerticalOffset.current = event.nativeEvent.contentOffset.y;
             scrollOffsetRef.current = event.nativeEvent.contentOffset.y;
             onScroll?.(event);
             if (shouldScrollToEndAfterLayout && (!hasCreatedActionAdded || isOffline)) {
@@ -377,7 +375,7 @@ function ReportActionsList({
 
     useEffect(() => {
         if (
-            scrollingVerticalOffset.current < AUTOSCROLL_TO_TOP_THRESHOLD &&
+            scrollOffsetRef.current < AUTOSCROLL_TO_TOP_THRESHOLD &&
             previousLastIndex.current !== lastActionIndex &&
             reportActionSize.current !== sortedVisibleReportActions.length &&
             hasNewestReportAction
diff --git a/tests/perf-test/ReportActionsList.perf-test.tsx b/tests/perf-test/ReportActionsList.perf-test.tsx
index 309e5c289b4..72122f8bf02 100644
--- a/tests/perf-test/ReportActionsList.perf-test.tsx
+++ b/tests/perf-test/ReportActionsList.perf-test.tsx
@@ -70,7 +70,7 @@ beforeAll(() =>
 const mockOnLayout = jest.fn();
 const mockOnScroll = jest.fn();
 const mockLoadChats = jest.fn();
-const mockRef = {current: null, flatListRef: null, scrollPosition: null, setScrollPosition: () => {}, scrollOffsetRef: {current: 0}};
+const mockRef = {current: null, flatListRef: null, scrollPositionRef: {current: {}}, scrollOffsetRef: {current: 0}};
 
 const TEST_USER_ACCOUNT_ID = 1;
 const TEST_USER_LOGIN = 'test@test.com';

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants