metamask-mobile icon indicating copy to clipboard operation
metamask-mobile copied to clipboard

[Bug]: Flashing on all bottom sheets in Send flow

Open Tlees-MMI opened this issue 3 weeks ago • 1 comments

Describe the bug

When clicking into parts of the confirmation screen bottom sheets, there is consistent flashing when clicking again.

Expected behavior

It shouldn't flash when opening bottom sheets through the send flow.

Screenshots/Recordings

https://github.com/user-attachments/assets/e55d2f7b-c70e-46dc-b578-508df0704e5b https://github.com/user-attachments/assets/e4513be4-6156-4144-9129-2d6aa1ace3ec

Steps to reproduce

  1. Do a send transaction
  2. Click on any pill where a bottom sheet propagates
  3. Click outside of the bottom sheet
  4. Notice flash

Error messages or log output


Detection stage

During release testing

Version

7.61

Build number

3242

Build type

None

Device

iPhone Max Pro 13

Operating system

iOS

Additional context

No response

Severity

No response

Tlees-MMI avatar Dec 08 '25 16:12 Tlees-MMI

Cursor Analysis

⚠️ Note: This is an AI-generated analysis based on user-submitted issue content. Please verify suggestions before implementing.

Analysis

1. Problem

Bottom sheets flash when opening from pills in the Send flow confirmation screen, especially when reopening after closing. The sheet briefly appears in the wrong position before animating.

2. Root Cause

Two likely causes:

Cause 1: State not reset on screen reuse

  • BottomSheetDialog initializes currentYOffset to screenHeight (off-screen).
  • The opening animation runs only after layout measurement (onLayout).
  • When React Navigation reuses a screen, isMounted.current stays true, so the opening animation may be skipped, causing a flash.

Cause 2: Initial render visibility

  • The sheet renders with currentYOffset = screenHeight before onLayout fires.
  • On screen reuse, it may briefly show the previous animated state before resetting.

3. Target Repo

This repo (MetaMask Mobile). Fix in:

  • app/component-library/components/BottomSheets/BottomSheet/foundation/BottomSheetDialog/BottomSheetDialog.tsx

4. Solutions

Solution 1: Reset state on unmount/focus (recommended)

  • Reset isMounted and currentYOffset when the component unmounts or loses focus.
  • Use useFocusEffect to handle screen focus/blur.

Pros:

  • Handles screen reuse correctly
  • Minimal changes
  • Works with React Navigation lifecycle

Cons:

  • Requires adding navigation focus handling

Solution 2: Hide sheet until layout is measured

  • Start with opacity: 0 or display: 'none' until the first layout measurement completes.

Pros:

  • Prevents visual flash
  • Simple change

Cons:

  • May cause a brief blank state
  • Doesn’t address root cause

Solution 3: Reset on navigation events

  • Use navigation listeners to reset state when navigating away/back.

Pros:

  • Explicit control over state

Cons:

  • More complex
  • Requires navigation prop access

Recommended Fix

Implement Solution 1: reset state on unmount and use focus effects.

// app/component-library/components/BottomSheets/BottomSheet/foundation/BottomSheetDialog/BottomSheetDialog.tsx

// Add import
import { useFocusEffect } from '@react-navigation/native';

// Inside BottomSheetDialog component, add:

// Reset state when component unmounts or loses focus
useFocusEffect(
  useCallback(() => {
    // Reset on mount/focus
    isMounted.current = false;
    currentYOffset.value = screenHeight;
    
    return () => {
      // Cleanup on unmount/blur
      isMounted.current = false;
      currentYOffset.value = screenHeight;
    };
  }, [screenHeight, currentYOffset])
);

// Also add cleanup in useEffect for unmount
useEffect(() => {
  return () => {
    isMounted.current = false;
    currentYOffset.value = screenHeight;
  };
}, [screenHeight, currentYOffset]);

Code Diff:

--- a/app/component-library/components/BottomSheets/BottomSheet/foundation/BottomSheetDialog/BottomSheetDialog.tsx
+++ b/app/component-library/components/BottomSheets/BottomSheet/foundation/BottomSheetDialog/BottomSheetDialog.tsx
@@ -1,6 +1,7 @@
 /* eslint-disable react/prop-types */
 
 // Third party dependencies.
+import { useFocusEffect } from '@react-navigation/native';
 import React, {
   forwardRef,
   useCallback,
@@ -191,6 +192,20 @@ const BottomSheetDialog = forwardRef<
       [onCloseDialog],
     );
 
+    // Reset state when screen gains/loses focus to prevent flashing on screen reuse
+    useFocusEffect(
+      useCallback(() => {
+        // Reset on mount/focus
+        isMounted.current = false;
+        currentYOffset.value = screenHeight;
+        
+        return () => {
+          // Cleanup on unmount/blur
+          isMounted.current = false;
+          currentYOffset.value = screenHeight;
+        };
+      }, [screenHeight, currentYOffset])
+    );
+
     useEffect(
       () =>
         // Automatically handles animation when content changes

Note: useFocusEffect requires the component to be within a navigation context. If not available, use a useEffect cleanup instead:

useEffect(() => {
  // Reset on mount
  isMounted.current = false;
  currentYOffset.value = screenHeight;
  
  return () => {
    // Reset on unmount
    isMounted.current = false;
    currentYOffset.value = screenHeight;
  };
}, [screenHeight, currentYOffset]);

This ensures the bottom sheet resets to its initial state when the screen is reused, preventing the flash.


Automated analysis by Cursor CLI

github-actions[bot] avatar Dec 15 '25 08:12 github-actions[bot]