Skip to content

RefreshControl flickering with largeTitle on iOS when using ScrollView or Flatlist #8074

@snaerth

Description

@snaerth

What happened?

When using largeTitle: { visible: true } with a ScrollView or FlatList containing a RefreshControl, the refresh control spinner flickers/jumps during pull-to-refresh on iOS. This creates a poor user experience.
Also the fade transition from big to small is janky.
Note! This works well if largeText is not visible.

Steps to Reproduce

  1. Create a screen with largeTitle: { visible: true } in navigation options
  2. Use a ScrollView or FlatList with RefreshControl as content
  3. Pull down to refresh on iOS
  4. Observe flickering/jumping of the refresh control
ScreenRecording_07-29-2025.15-32-01_1.MP4

What was the expected behaviour?

The refresh control should smoothly animate without flickering.

Was it tested on latest react-native-navigation?

  • I have tested this issue on the latest react-native-navigation release and it still reproduces.

Help us reproduce this issue!

Code Example

import React, { useState } from 'react'
import {
  RefreshControl,
  SafeAreaView,
  ScrollView,
  Text,
  View,
} from 'react-native'
import { NavigationFunctionComponent } from 'react-native-navigation'

// Dummy data for demonstration
const DUMMY_ITEMS = Array.from({ length: 20 }, (_, i) => ({
  id: i,
  title: `Item ${i + 1}`,
  description: `This is a description for item ${i + 1}`,
}))

export const DummScreen: NavigationFunctionComponent = () => {
  const [refreshing, setRefreshing] = useState(false)

  const onRefresh = async () => {
    setRefreshing(true)
    // Simulate API call
    await new Promise((resolve) => setTimeout(resolve, 2000))
    setRefreshing(false)
  }

  return (
    <SafeAreaView style={{ flex: 1 }}>
      <ScrollView
        refreshControl={
          <RefreshControl refreshing={refreshing} onRefresh={onRefresh} />
        }
        contentContainerStyle={{ padding: 16 }}
      >
        {DUMMY_ITEMS.map((item) => (
          <View
            key={item.id}
            style={{
              padding: 16,
              marginBottom: 8,
              backgroundColor: '#f0f0f0',
              borderRadius: 8,
            }}
          >
            <Text style={{ fontSize: 18, fontWeight: 'bold' }}>
              {item.title}
            </Text>
            <Text style={{ fontSize: 14, color: '#666', marginTop: 4 }}>
              {item.description}
            </Text>
          </View>
        ))}
      </ScrollView>
    </SafeAreaView>
  )
}

// Static options following the docs example
ApplicationsScreen.options = {
  topBar: {
    title: {
      text: 'Dummy Screen',
    },
    largeTitle: {
      visible: true, // This causes the flickering issue
    },
    scrollEdgeAppearance: {
      active: true,
      noBorder: true,
    },
  },
}

In what environment did this happen?

Environment

  • React Native Navigation version: 8.2.1 (patched)
  • React Native version: 0.77.2 (patched)
  • Has Fabric (React Native's new rendering system) enabled: yes
  • Node version: 20.15.0
  • Device model: iPhone 15 Pro

Patches

React native navigation patch

diff --git a/ReactNativeNavigation.podspec b/ReactNativeNavigation.podspec
index 1777dc62b018f884dcee620f9b404ec37f49c683..24eb71637993f5e2fa01673386e1882ca612dd99 100644
--- a/ReactNativeNavigation.podspec
+++ b/ReactNativeNavigation.podspec
@@ -44,7 +44,9 @@ Pod::Spec.new do |s|
     # Only expose headers for Swift projects
     if swift_project
       s.public_header_files = [
-          'lib/ios/RNNAppDelegate.h'
+          'lib/ios/RNNAppDelegate.h',
+          'lib/ios/ReactNativeNavigation.h',
+          'lib/ios/TurboModules/RNNTurboManager.h'
         ]
     end
   end

React native patch

diff --git a/ReactCommon/react/runtime/platform/ios/ReactCommon/RCTHost.mm b/ReactCommon/react/runtime/platform/ios/ReactCommon/RCTHost.mm
index b796713ed0670a50aa8dddcc32b8732058f0bb47..5142307e8b94faba52cbcc1cdb9556f00f1faa1c 100644
--- a/ReactCommon/react/runtime/platform/ios/ReactCommon/RCTHost.mm
+++ b/ReactCommon/react/runtime/platform/ios/ReactCommon/RCTHost.mm
@@ -254,7 +254,9 @@ - (RCTFabricSurface *)createSurfaceWithModuleName:(NSString *)moduleName
   surface.surfaceHandler.setDisplayMode(displayMode);
   [self _attachSurface:surface];
 
-  [_instance callFunctionOnBufferedRuntimeExecutor:[surface](facebook::jsi::Runtime &_) { [surface start]; }];
+  __weak RCTFabricSurface *weakSurface = surface;
+  // Use the BufferedRuntimeExecutor to start the surface after the main JS bundle was fully executed.
+  [_instance callFunctionOnBufferedRuntimeExecutor:[weakSurface](facebook::jsi::Runtime &_) { [weakSurface start]; }];
   return surface;
 }
 

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions