Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Unresponsive modal when RefreshControl used with iOS modal and header hidden #2586

Closed
chvanlennep opened this issue Dec 20, 2024 · 6 comments · May be fixed by #2610
Closed

Unresponsive modal when RefreshControl used with iOS modal and header hidden #2586

chvanlennep opened this issue Dec 20, 2024 · 6 comments · May be fixed by #2610
Assignees
Labels
Platform: iOS This issue is specific to iOS Repro provided A reproduction with a snack or repo is provided

Comments

@chvanlennep
Copy link

chvanlennep commented Dec 20, 2024

Description

When displaying a native iOS modal using react-navigation/native-stack, if the screen contains a RefreshControl component in a scrollable component and headerShown: false is set on the screen options, the modal is unresponsive on mount. Backgrounding the app and returning fixes the issue.

This happens on simulators and real devices

Link to same issue on react navigation repo: https://github.com/react-navigation/react-navigation/issues/12355

Steps to reproduce

  1. Navigate to a screen containing a RefreshControl component with presentation: modal and headerShown: false set in screen options
  2. Screen mounts but is unresponsive
  3. Background app and return to app, screen is again responsive

Snack or a link to a repository

https://github.com/chvanlennep/RefreshControlExample

Screens version

4.1.0

React Native version

0.76.3

Platforms

iOS

JavaScript runtime

Hermes

Workflow

Expo bare workflow

Architecture

Fabric (New Architecture)

Build type

Debug mode

Device

iOS simulator

Device model

No response

Acknowledgements

Yes

@github-actions github-actions bot added Platform: iOS This issue is specific to iOS Repro provided A reproduction with a snack or repo is provided labels Dec 20, 2024
@kkafar kkafar self-assigned this Dec 20, 2024
@kkafar
Copy link
Member

kkafar commented Jan 9, 2025

Confirming the issue, thanks for the report!

@kkafar
Copy link
Member

kkafar commented Jan 9, 2025

I do not understand the error mechanism yet, nor do I understand why setting headerShown: true helps to resolve it, however I've observed that delaying mounting the refresh control by single run loop iteration helps to resolve the problem.

Following patch to RCTPullToRefreshViewComponentView.mm resolves the issue:

diff --git a/packages/react-native/React/Fabric/Mounting/ComponentViews/ScrollView/RCTPullToRefreshViewComponentView.mm b/packages/react-native/React/Fabric/Mounting/ComponentViews/ScrollView/RCTPullToRefreshViewComponentView.mm
index 3a5679ea5dd..f9bad6c23c2 100644
--- a/packages/react-native/React/Fabric/Mounting/ComponentViews/ScrollView/RCTPullToRefreshViewComponentView.mm
+++ b/packages/react-native/React/Fabric/Mounting/ComponentViews/ScrollView/RCTPullToRefreshViewComponentView.mm
@@ -148,9 +148,13 @@ using namespace facebook::react;
 {
   [super didMoveToWindow];
   if (self.window) {
-    [self _attach];
+    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 0), dispatch_get_main_queue(), ^{
+      [self _attach];
+    });
   } else {
-    [self _detach];
+    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 0), dispatch_get_main_queue(), ^{
+      [self _detach];
+    });
   }
 }

@chvanlennep in case it's urgent, you can patch react-native, you build this code anyway.

@kkafar
Copy link
Member

kkafar commented Jan 9, 2025

New finding: this also affects standard react-native modal with animationType set. Without animation there is no bug.

@kkafar
Copy link
Member

kkafar commented Jan 9, 2025

New finding:

it affects bare RN app without react-native-screens or react-navigation.

Minimal repro:

import React from 'react';
import { StyleSheet, View, ScrollView, RefreshControl, Button as RNButton, Modal, Text } from 'react-native';

export function Home() {
  const [modalVisible, setModalVisible] = React.useState(false);

  return (
    <View style={styles.container}>
      <Text>Home Screen</Text>
      <Text>Open up 'src/App.tsx' to start working on your app!</Text>
      <RNButton title='Toggle modal' onPress={() => setModalVisible(true)} />
      <Modal visible={modalVisible} presentationStyle='formSheet' animationType='slide' >
        <Settings closeModal={() => setModalVisible(false)}/>
      </Modal>
    </View>
  );
}

export function Settings({ closeModal }: { closeModal?: () => void }) {
  return (
    <View style={styles.container}>
      <ScrollView
        refreshControl={
          <RefreshControl refreshing={false} onRefresh={() => {}} />
        }
      >
        <Text style={styles.text}>Empty List</Text>
        <RNButton title="Press me" onPress={() => console.log('Pressed!')} />
        {closeModal && (
          <RNButton title='Close modal' onPress={closeModal} />
        )}
      </ScrollView>
    </View>
  );
}


export default function BareReactNativeApp() {
  return <Home />;
}

@kkafar
Copy link
Member

kkafar commented Jan 9, 2025

I think the best way to proceed now would be to open an issue on react-native's repository as this seems to be something wrong about refresh control itself.

I'll close this ticket once I move the issue there & suggest my patch as a fix.

@kkafar
Copy link
Member

kkafar commented Jan 9, 2025

Opened issue on RN repo: facebook/react-native#48579

@kkafar kkafar closed this as completed Jan 9, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Platform: iOS This issue is specific to iOS Repro provided A reproduction with a snack or repo is provided
Projects
None yet
2 participants