Skip to content

FlatList: Pull to Refresh refresh controls are "freezed" after navigation.goBack() #41515

Description

@ivanignatiev

Description

I cannot find any open issue for this topic.

I have 2 screens inside same context, useEffect depend on both screens depend on the same state from common context.

When I update context state data on Screen 2, and goBack()to Screen 1, refresh controls for flatlist are just freezed and I need to pull it to make it disappear:

State with refreshingvalue if changing to false correctly.

React Native Version

0.73.0-rc.4

Output of npx react-native info

System:
OS: macOS 14.1.1
CPU: (12) arm64 Apple M3 Pro
Memory: 68.31 MB / 18.00 GB
Shell:
version: "5.9"
path: /bin/zsh
Binaries:
Node:
version: 21.1.0
path: /opt/homebrew/bin/node
Yarn:
version: 1.22.19
path: /opt/homebrew/bin/yarn
npm:
version: 10.2.0
path: /opt/homebrew/bin/npm
Watchman:
version: 2023.11.06.00
path: /opt/homebrew/bin/watchman
Managers:
CocoaPods:
version: 1.14.2
path: /opt/homebrew/bin/pod
SDKs:
iOS SDK:
Platforms:
- DriverKit 23.0
- iOS 17.0
- macOS 14.0
- tvOS 17.0
- watchOS 10.0
Android SDK: Not Found
IDEs:
Android Studio: 2022.3 AI-223.8836.35.2231.11005911
Xcode:
version: 15.0.1/15A507
path: /usr/bin/xcodebuild
Languages:
Java:
version: 17.0.9
path: /usr/bin/javac
Ruby:
version: 2.6.10
path: /usr/bin/ruby
npmPackages:
"@react-native-community/cli": Not Found
react:
installed: 18.2.0
wanted: 18.2.0
react-native:
installed: 0.73.0-rc.4
wanted: 0.73.0-rc.4
react-native-macos: Not Found
npmGlobalPackages:
"react-native": Not Found
Android:
hermesEnabled: true
newArchEnabled: false
iOS:
hermesEnabled: true
newArchEnabled: false

Steps to reproduce

  1. Make 2 screens inside same context
  2. Use FlatList on screen 1 and screen 2
  3. Use useEffect dependent on the same state in the common context
  4. go to Screen 2 , modify state in the common context
  5. use navigation.goBack()
  6. See that refresh controls are still there even if refreshingis false

Snack, screenshot, or link to a repository

Screenshot by Dropbox Capture

Activity

  1. github-actions commented on Nov 16, 2023

    @github-actions
    ⚠️ Missing Reproducible Example
    ℹ️ We could not detect a reproducible example in your issue report. Please provide either:
    • If your bug is UI related: a Snack
    • If your bug is build/update related: use our Reproducer Template. A reproducer needs to be in a GitHub repository under your username.
  2. huiqun922 commented on Nov 18, 2023

    @huiqun922

    +1 same isuues. when switch tab or back to desktop and return app.
    screen loss of focus happens。
    only iOS

  3. cortinico commented on Nov 22, 2023

    @cortinico
    Contributor

    0.73.0-rc.4

    Is this a regression from 0.72?

  4. huiqun922 commented on Nov 22, 2023

    @huiqun922

    0.73.0-rc.4

    Is this a regression from 0.72?

    0.72.4

  5. ivanignatiev commented on Nov 22, 2023

    @ivanignatiev
    Author

    My observations, it is happening only when I write logs, so they are showing in metro. In release build with logs turned off everything is working as expected.

    Maybe communication with metro in async operation is breaking something.

  6. cortinico commented on Nov 23, 2023

    @cortinico
    Contributor

    @ivanignatiev can you try on 0.72 and confirm if the issue persists there or not?

  7. huiqun922 commented on Nov 30, 2023

    @huiqun922

    @cortinico i have same issue on 0.72.1

    Demo Repository: https://github.com/huiqun922/rn_nav.git

    2023-11-30.14.24.28.mov
  8. huanguolin commented on Jun 4, 2024

    @huanguolin

    same issue with 0.72.12

  9. jdarshan5 commented on Jun 3, 2025

    @jdarshan5

    +1

  10. jsonxr commented on Sep 16, 2025

    @jsonxr

    Still issue on 0.81

  11. JacobJaffe commented on Oct 9, 2026

    @JacobJaffe

    I've been fighting this bug for years, and I just let Claude have a crack at it. The future is scary and impressive. Here's my fix for this now, for any who dare attempt it...


    There are actually two separate bugs with similar symptoms, both caused by the refresh control being detached from the window while its screen is inactive. With react-native-screens, inactive bottom tabs and covered native-stack screens are detached.

    Environment

    • Expo SDK 54, React Native 0.81.5, old architecture (newArchEnabled: false). Can't move to the new architecture yet because of problems with Reanimated scroll handlers.
    • react-native-screens ~4.16, @react-navigation/bottom-tabs 7 and native-stack 7
    • FlashList 1.7.6 wrapped with Animated.createAnimatedComponent, plus plain FlatLists. It isn't list-specific.
    • Built from source (not prebuilt RN core), tested on iOS 18.7.8

    Bug 1: refresh ends while detached, spinner stuck forever

    Repro: pull to refresh, then switch tabs or goBack() before the fetch finishes. JS sets refreshing={false} while the screen is inactive. When you return, the spinner is frozen, the content stays pushed down, and scrolling doesn't clear it.

    Cause: endRefreshingProgrammatically returns early when !_hasMovedToWindow, but setRefreshing: has already set _currentRefreshingState = NO. Nothing replays the skipped endRefreshing when the control re-enters a window. JS state and UIKit state stay out of sync permanently.

    Bug 2: idle control drawn as "pulled" after re-attach

    Repro: open a screen whose list is still on its initial load, navigate away during the load, and return after it finishes. refreshing was never true, but a frozen spinner is visible. Unlike bug 1, scrolling clears it.

    Cause: logging showed super.refreshing == NO and _currentRefreshingState == NO the whole time, and no valueChanged fired. UIKit was never refreshing. The scroll view's content inset changed during the initial load (from -50 to 48.3 in our case), and after the detach and re-attach, UIKit lays out the idle control as partially pulled. This looks like the issue #31024 fixed by calling endRefreshing in didMoveToWindow. That call isn't in the current didMoveToWindow, which only toggles _hasMovedToWindow.

    Fix (patch-package)

    node_modules/react-native/React/Views/RefreshControl/RCTRefreshControl.m

    Reconcile the two states one tick after the control re-attaches to a window:

    - (void)didMoveToWindow
    {
      [super didMoveToWindow];
    
      if (self.window) {
        _hasMovedToWindow = YES;
        UInt64 timestamp = _currentRefreshingStateTimestamp;
        dispatch_async(dispatch_get_main_queue(), ^{
          if (!self.window || timestamp != self->_currentRefreshingStateTimestamp) {
            return; // detached again, or setRefreshing: already ran while attached
          }
          [self _reconcileRefreshingAfterReattach];
        });
      } else {
        _hasMovedToWindow = NO;
      }
    }
    
    - (void)_reconcileRefreshingAfterReattach
    {
      if (_currentRefreshingState) {
        return;
      }
    
      if (!super.refreshing) {
        // Bug 2: reset idle layout (what #31024 did), and re-trigger the
        // control's offset observation the same way a manual scroll does.
        [super endRefreshing];
        UIScrollView *scrollView = self.scrollView;
        if (scrollView) {
          [scrollView setContentOffset:scrollView.contentOffset animated:NO];
        }
        return;
      }
    
      // Bug 1: replay the endRefreshing that was skipped while detached.
      UIScrollView *scrollView = self.scrollView;
      if (scrollView && scrollView.contentOffset.y < -scrollView.contentInset.top) {
        UInt64 ts = _currentRefreshingStateTimestamp;
        CGPoint offset = {scrollView.contentOffset.x, -scrollView.contentInset.top};
        [UIView animateWithDuration:0.25
            delay:0
            options:UIViewAnimationOptionBeginFromCurrentState
            animations:^{ [scrollView setContentOffset:offset]; }
            completion:^(__unused BOOL finished) {
              if (ts == self->_currentRefreshingStateTimestamp) {
                [super endRefreshing];
              }
            }];
      } else {
        [super endRefreshing];
      }
    }
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions