facebook / react-native

A framework for building native applications using React
https://reactnative.dev
MIT License
119.56k stars 24.37k forks source link

No haptic feedback when using RefreshControl with a tintColor prop #43388

Open marcshilling opened 8 months ago

marcshilling commented 8 months ago

Description

On iOS, it's expected that the system gives small haptic feedback when using pull-to-refresh. This works by default when using <RefreshControl>. However, when passing a tintColor prop to <RefreshControl>, the haptics don't play.

I don't have another device to confirm, but I feel like this may have just started recently, possibly with iOS 17.4.

Steps to reproduce

Use a RefreshControl with a tintColor prop on a real iOS device and observe that the haptic feedback does not play. Comment out the tintColor prop, reload the app (fast refresh doesn't have an effect), and then observe the haptic feedback works.

React Native Version

0.73.5

Affected Platforms

Runtime - iOS

Device I'm testing on is an iPhone 15 Pro on iOS 17.4

Output of npx react-native info

System:
  OS: macOS 14.3.1
  CPU: (16) x64 Intel(R) Core(TM) i9-9980HK CPU @ 2.40GHz
  Memory: 2.40 GB / 16.00 GB
  Shell:
    version: "5.9"
    path: /bin/zsh
Binaries:
  Node:
    version: 19.6.0
    path: ~/.asdf/installs/nodejs/19.6.0/bin/node
  Yarn:
    version: 1.22.19
    path: /usr/local/bin/yarn
  npm:
    version: 9.4.0
    path: ~/.asdf/plugins/nodejs/shims/npm
  Watchman: Not Found
Managers:
  CocoaPods:
    version: 1.14.3
    path: /Users/marcshilling/.asdf/shims/pod
SDKs:
  iOS SDK:
    Platforms:
      - DriverKit 23.2
      - iOS 17.2
      - macOS 14.2
      - tvOS 17.2
      - visionOS 1.0
      - watchOS 10.2
  Android SDK: Not Found
IDEs:
  Android Studio: 2023.1 AI-231.9392.1.2311.11330709
  Xcode:
    version: 15.2/15C500b
    path: /usr/bin/xcodebuild
Languages:
  Java:
    version: 11.0.18
    path: /usr/bin/javac
  Ruby:
    version: 2.7.6
    path: /Users/marcshilling/.asdf/shims/ruby
npmPackages:
  "@react-native-community/cli": Not Found
  react:
    installed: 18.2.0
    wanted: 18.2.0
  react-native:
    installed: 0.73.5
    wanted: 0.73.5
  react-native-macos: Not Found
npmGlobalPackages:
  "*react-native*": Not Found
Android:
  hermesEnabled: true
  newArchEnabled: false
iOS:
  hermesEnabled: true
  newArchEnabled: false

Stacktrace or Logs

N/A

Reproducer

https://github.com/marcshilling/react-native-refresh-control-haptics-bug

Screenshots and Videos

No response

matiaswastaken commented 7 months ago

This works as expected with an iOS version lower than 17.4, so something definitely has broken in iOS 17.4

dus7 commented 5 months ago

I stumbled on the same thing and it looks like UIRefreshControl needs to be set on a scroll view before the tintColor is set.

haileyok commented 1 month ago

Got around to writing a patch for this here https://github.com/bluesky-social/social-app/pull/5605 based on @dus7's finding. Thanks!

Still an issue in iOS 18 unfortunately.

sobrinho commented 2 weeks ago

Here's my workaround in pure react:

import { useState, useEffect } from "react";
import {
  RefreshControl as RNRefreshControl,
  RefreshControlProps as RNRefreshControlProps
} from "react-native";

export default function RefreshControl(props: RNRefreshControlProps) {
  const [tintColor, setTintColor] = useState<RNRefreshControlProps["tintColor"]>();

  // Shameless workaround to set the tint color after the component is rendered.
  // See https://github.com/facebook/react-native/issues/43388
  useEffect(() => {
    setTintColor(props.tintColor);

    // If you want a default tintColor:
    // setTintColor(props.tintColor || MY_DEFAULT_TINT_COLOR);
  }, [props.tintColor]);

  return (
    <RNRefreshControl
      {...props}
      tintColor={tintColor}
    />
  );
}

Explaining the workaround:

  1. The RNRefreshControl is mounted without a tintColor since we initialize the tintColor state without a value
  2. Then we call an effect after the RNRefreshControl is already mounted to update the tintColor to the props.tintColor
  3. If the prop is altered, we run the effect again by reacting to the prop

Tested on a real device and it worked.