Skip to content

Bump to React Native 0.87 - #4310

Draft
m-bert wants to merge 8 commits into
mainfrom
@mbert/support-87
Draft

Bump to React Native 0.87#4310
m-bert wants to merge 8 commits into
mainfrom
@mbert/support-87

Conversation

@m-bert

@m-bert m-bert commented Jul 10, 2026

Copy link
Copy Markdown
Collaborator

Description

This PR brings support for React Native 0.87

Status

  • 0.87.0-rc.3 ✅

  • 0.87.0-rc.2 ✅
  • 0.87.0-rc.1 ✅
  • 0.87.0-rc.0 ✅

Required changes

Test plan

Tested that basic-example builds and works correctly.

Summary by CodeRabbit

  • New Features

    • Enabled edge-to-edge display support in the Android example app.
  • Improvements

    • Updated the example app and development environment for React Native 0.87.
    • Upgraded Android build tooling, SDK, Kotlin, and Gradle versions.
    • Improved release builds with optimized code shrinking.
  • Bug Fixes

    • Improved style handling to prevent unintended layout mutations.
    • Updated scroll view and touchable compatibility for newer React Native APIs.
  • Maintenance

    • Refined TypeScript configuration and modernized example app type definitions.

Copilot AI review requested due to automatic review settings July 10, 2026 08:57

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR updates the monorepo’s development and example-app setup to align with React Native 0.87.0-rc.0, including corresponding tooling upgrades for Android, iOS pods, and TypeScript.

Changes:

  • Bump React Native and React Native tooling/presets to 0.87.0-rc.0 in the library dev setup and basic-example.
  • Update basic-example TypeScript configuration for TS 6 (and align tsconfig types with other apps).
  • Refresh basic-example native build tooling: Gradle wrapper/SDK levels, Gradle properties, and iOS Podfile.lock.

Reviewed changes

Copilot reviewed 8 out of 11 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
packages/react-native-gesture-handler/package.json Updates RN-related devDependencies used for local development/tests to 0.87.0-rc.0.
apps/basic-example/tsconfig.json TS config updates for TS 6 and removes Jest types from the app’s TS compilation context.
apps/basic-example/package.json Bumps RN + RN toolchain (CLI/presets/configs) and upgrades TypeScript to ~6.0.3.
apps/basic-example/ios/Podfile.lock Updates iOS pod resolution for RN 0.87.0-rc.0 and related native deps.
apps/basic-example/android/gradlew Updates the Gradle wrapper script reference URL comment to a pinned Gradle commit.
apps/basic-example/android/gradle/wrapper/gradle-wrapper.properties Updates the Gradle distribution version used by the example app.
apps/basic-example/android/gradle.properties Enables edge-to-edge and adds AGP 9 opt-out flags.
apps/basic-example/android/build.gradle Updates Android SDK/build tools and Kotlin version used by the example app build.
apps/basic-example/android/app/build.gradle Switches default ProGuard file to the optimized variant.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread apps/basic-example/android/build.gradle
@m-bert m-bert added the New React Native version support This pull request aims to bring support for new React Native version label Jul 20, 2026
@m-bert m-bert changed the title Support React Native 0.87 Bump to React Native 0.87 Jul 28, 2026
@rcybulski1122012

Copy link
Copy Markdown
Member

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Aug 3, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai

coderabbitai Bot commented Aug 3, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The pull request updates the basic example to React Native 0.87.0-rc.3 and newer Android tooling. It also updates common-app TypeScript references, disables declaration output, clones flattened styles before mutation, and adds lint suppressions.

Changes

React Native 0.87 compatibility

Layer / File(s) Summary
Example toolchain and React Native upgrade
apps/basic-example/android/..., apps/basic-example/ios/..., apps/basic-example/package.json, apps/basic-example/tsconfig.json, packages/react-native-gesture-handler/package.json
The example and package use React Native 0.87.0-rc.3, updated CLI packages, Android tooling, Gradle 9.4.1, TypeScript settings, edge-to-edge support, and reordered privacy entries.
React Native type compatibility
apps/common-app/src/legacy/..., apps/common-app/src/new_api/..., apps/common-app/tsconfig.json
Animated value annotations no longer specify numeric generics. Scroll view refs derive from component refs. The touchable background type derives from component props. Declaration output is disabled.
Lint and style adjustments
apps/common-app/src/ListWithHeader/..., packages/react-native-gesture-handler/src/components/...
Flattened content styles are cloned before mutation. Lint suppressions cover extracted React Native methods and Animated.timing.

Estimated code review effort: 2 (Simple) | ~10 minutes

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main change: updating project support to React Native 0.87.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch @mbert/support-87

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

ESLint install failed. For unrecoverable errors, disable the tool in CodeRabbit configuration.


Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@apps/basic-example/android/gradle.properties`:
- Line 44: Replace the fixed Android padding associated with edgeToEdgeEnabled
in the relevant SafeAreaView layout with runtime inset handling via
react-native-safe-area-context or an equivalent Android inset API. Apply
status-bar and display-cutout insets dynamically, including landscape behavior,
and remove the hardcoded paddingTop value.

In `@apps/common-app/src/legacy/recipes/panAndScroll/index.tsx`:
- Line 27: Restore the required React Native Animated generic arguments across
the affected declarations: in
apps/common-app/src/legacy/recipes/panAndScroll/index.tsx:27 and
apps/common-app/src/legacy/recipes/scaleAndRotate/index.tsx:26, use
Animated.AnimatedAddition<number> for both translateX fields; in
apps/common-app/src/legacy/recipes/scaleAndRotate/index.tsx:36 and
apps/common-app/src/legacy/release_tests/nestedFling/index.tsx:17, use
Animated.AnimatedMultiplication<number> for scale and reverseLastScrollY; and in
apps/common-app/src/legacy/showcase/bottomSheet/index.tsx:41, use
Animated.AnimatedInterpolation<string> for tiltStr.

In `@apps/common-app/src/legacy/release_tests/combo/index.tsx`:
- Around line 138-140: Update the ref callback assigning this.scrollView to use
a block body, ensuring the assignment executes without implicitly returning its
value and the callback returns nothing.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 5d75fd0a-6a08-49b1-96ae-8176f7199d89

📥 Commits

Reviewing files that changed from the base of the PR and between 7d2f321 and de62f62.

⛔ Files ignored due to path filters (3)
  • apps/basic-example/android/gradle/wrapper/gradle-wrapper.jar is excluded by !**/*.jar
  • apps/basic-example/ios/Podfile.lock is excluded by !**/*.lock
  • yarn.lock is excluded by !**/yarn.lock, !**/*.lock
📒 Files selected for processing (22)
  • apps/basic-example/android/app/build.gradle
  • apps/basic-example/android/build.gradle
  • apps/basic-example/android/gradle.properties
  • apps/basic-example/android/gradle/wrapper/gradle-wrapper.properties
  • apps/basic-example/android/gradlew
  • apps/basic-example/ios/BasicExample/PrivacyInfo.xcprivacy
  • apps/basic-example/package.json
  • apps/basic-example/src/Navigator.tsx
  • apps/basic-example/tsconfig.json
  • apps/common-app/src/ListWithHeader/ListWithHeader.tsx
  • apps/common-app/src/legacy/recipes/panAndScroll/index.tsx
  • apps/common-app/src/legacy/recipes/scaleAndRotate/index.tsx
  • apps/common-app/src/legacy/release_tests/combo/index.tsx
  • apps/common-app/src/legacy/release_tests/nestedFling/index.tsx
  • apps/common-app/src/legacy/release_tests/touchables/index.tsx
  • apps/common-app/src/legacy/showcase/bottomSheet/index.tsx
  • apps/common-app/src/legacy/v2_api/calculator/index.tsx
  • apps/common-app/src/new_api/components/scrollview/index.tsx
  • apps/common-app/tsconfig.json
  • packages/react-native-gesture-handler/package.json
  • packages/react-native-gesture-handler/src/components/ReanimatedDrawerLayout.tsx
  • packages/react-native-gesture-handler/src/components/touchables/TouchableOpacity.tsx

# This allows your app to draw behind system bars for an immersive UI.
# Note: Only works with ReactActivity and should not be used with custom Activity.
edgeToEdgeEnabled=false
edgeToEdgeEnabled=true

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -eu
printf '%s\n' '--- target configuration ---'
sed -n '35,52p' apps/basic-example/android/gradle.properties
printf '%s\n' '--- App.tsx relevant section ---'
sed -n '1,85p' apps/basic-example/src/App.tsx
printf '%s\n' '--- package and dependency references ---'
rg -n -C 3 'react-native-safe-area-context|SafeAreaView|edgeToEdgeEnabled|targetSdkVersion|compileSdkVersion' \
  apps/basic-example package.json apps/basic-example/package.json 2>/dev/null || true
printf '%s\n' '--- repository files related to Android inset handling ---'
git ls-files | rg '(^|/)(App\.tsx|.*safe.?area.*|gradle\.properties|build\.gradle|AndroidManifest\.xml)$' | head -200

Repository: software-mansion/react-native-gesture-handler

Length of output: 7566


🌐 Web query:

React Native SafeAreaView Android edge-to-edge support documentation edgeToEdgeEnabled

💡 Result:

For React Native applications targeting modern Android, edge-to-edge support is now a standard requirement, particularly as Android 15 and 16 enforce this behavior [1][2]. Enabling Edge-to-Edge Starting with React Native 0.81, you can enable edge-to-edge layout across supported Android versions by adding the following property to your android/gradle.properties file [3][2]: edgeToEdgeEnabled=true When this property is enabled, your app's content will flow behind the system bars (status bar and navigation bar) [3][4]. For applications targeting Android 15 (SDK 35) or higher, edge-to-edge is enforced by the platform [1][2]. Recommended Approach for Safe Areas The native React Native SafeAreaView component is generally discouraged for Android, as it was originally designed for iOS and lacks the necessary flexibility for robust Android edge-to-edge handling [3][5][6]. Instead, use the community-standard react-native-safe-area-context library [3][5][6]. Implementation Steps: 1. Configure the Provider: Wrap your root component in SafeAreaProvider [7][8]. 2. Use the Hook: Instead of the SafeAreaView component, use the useSafeAreaInsets hook to apply padding to your screens based on the device's safe area insets [3][5]. This ensures consistent behavior across both platforms [3][5]. Example: import { useSafeAreaInsets } from 'react-native-safe-area-context'; import { View } from 'react-native'; const MyComponent = => { const insets = useSafeAreaInsets; return ( <View style={{ paddingTop: insets.top, paddingBottom: insets.bottom }}> {/* Content */} ); }; Important Considerations: - Deprecation of Older Libraries: If you previously used the third-party react-native-edge-to-edge library, it is now largely superseded by the built-in edgeToEdgeEnabled Gradle property in React Native 0.81+ [9][2]. - System Bars: With edge-to-edge enabled, traditional APIs for setting status bar background colors may have reduced or no effect because the bars are now transparent [2][6]. You may need to use tools like react-native-navigation-bar if specific navigation bar styling is required [4][9]. - Testing: Always test on devices running Android 15+ to ensure your UI correctly handles system bar insets, as edge-to-edge enforcement cannot be opted out of on Android 16+ [2][10].

Citations:


🏁 Script executed:

#!/bin/bash
set -eu
printf '%s\n' '--- React Native version declarations ---'
rg -n -C 2 '"react-native"|reactNativeVersion|REACT_NATIVE_VERSION' \
  package.json apps/basic-example package.json apps/basic-example/android \
  2>/dev/null || true
printf '%s\n' '--- all basic-example layout and inset-related code ---'
rg -n -C 4 'SafeAreaView|paddingTop|paddingBottom|StatusBar|WindowInsets|insets|SafeAreaProvider|useSafeArea' \
  apps/basic-example/src apps/basic-example/android 2>/dev/null || true
printf '%s\n' '--- root scripts and lockfile entries ---'
rg -n -m 20 '"react-native"|"react-native-safe-area-context"' \
  package.json yarn.lock package-lock.json pnpm-lock.yaml 2>/dev/null || true
printf '%s\n' '--- Android activity configuration ---'
sed -n '1,180p' apps/basic-example/android/app/src/main/AndroidManifest.xml
rg -n -C 5 'class MainActivity|ReactActivity|edgeToEdge|WindowCompat|setDecorFitsSystemWindows|WindowInsets' \
  apps/basic-example/android apps/basic-example/src 2>/dev/null || true

Repository: software-mansion/react-native-gesture-handler

Length of output: 9472


🌐 Web query:

site:reactnative.dev/docs SafeAreaView Android iOS only deprecated react native

💡 Result:

The SafeAreaView component in React Native is deprecated and should not be used in new projects [1][2][3]. The official recommendation is to use the react-native-safe-area-context library instead, which provides a robust, cross-platform solution for handling safe area insets on both Android and iOS [1][2][4]. Historically, the built-in SafeAreaView component was limited to iOS devices (iOS 11+) and did not provide native support for Android safe area insets [5][1][6]. By contrast, react-native-safe-area-context is designed to work across platforms, accurately handling notches, home indicators, and other physical screen limitations on both operating systems [4]. Most modern React Native projects, including those using React Navigation, rely on this library as the standard approach [4].

Citations:


🌐 Web query:

site:github.com/facebook/react-native v0.87 SafeAreaView Android implementation edgeToEdgeEnabled

💡 Result:

In React Native, the built-in SafeAreaView component is deprecated and was primarily designed for iOS [1]. It does not provide cross-platform support for handling edge-to-edge insets on Android [1][2]. For modern Android applications targeting edge-to-edge (which is enforced by default on Android 15/API 35+), you should use the react-native-safe-area-context library instead of the core SafeAreaView component [1][2]. Regarding edge-to-edge configuration in React Native: 1. Gradle Property: The edge-to-edge behavior in React Native is controlled by the edgeToEdgeEnabled property in your app's gradle.properties file [3][4]. 2. Automatic Detection: As of recent releases (including v0.86 and v0.87), React Native has improved handling for edge-to-edge scenarios [5][6]. The framework now includes logic to detect when edge-to-edge is enforced by the OS (Android 15+), even if the edgeToEdgeEnabled property is not explicitly set to true [5]. 3. Implementation: When edge-to-edge is active, React Native automatically adjusts certain behaviors, such as disabling StatusBar backgroundColor and translucent properties to allow the app to render behind system bars [3]. To properly manage system bar overlaps in your layout on Android, it is recommended to use the useSafeAreaInsets hook provided by react-native-safe-area-context, which is the industry standard for this purpose [1][4][2].

Citations:


🏁 Script executed:

#!/bin/bash
set -eu
printf '%s\n' '--- navigator implementation ---'
fd -i 'Navigator' apps/basic-example/src --type f -x sh -c 'echo "--- $1"; sed -n "1,240p" "$1"' sh {}
printf '%s\n' '--- navigator header and container styles ---'
rg -n -C 5 'header|padding|SafeArea|StatusBar|View|Navigator' apps/basic-example/src/Navigator* apps/basic-example/src 2>/dev/null | head -240

Repository: software-mansion/react-native-gesture-handler

Length of output: 18022


Replace the fixed Android padding with runtime insets.

SafeAreaView does not provide Android edge-to-edge insets. The fixed paddingTop: 50 can leave an incorrect gap or overlap the status bar or display cutout. Use react-native-safe-area-context or another Android inset API, and test cutout and landscape layouts.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/basic-example/android/gradle.properties` at line 44, Replace the fixed
Android padding associated with edgeToEdgeEnabled in the relevant SafeAreaView
layout with runtime inset handling via react-native-safe-area-context or an
equivalent Android inset API. Apply status-bar and display-cutout insets
dynamically, including landscape behavior, and remove the hardcoded paddingTop
value.

Source: MCP tools

export class TapOrPan extends Component<Props> {
private touchX: Animated.Value;
private translateX: Animated.AnimatedAddition<number>;
private translateX: Animated.AnimatedAddition;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🔴 Critical | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

decl_file="$(find node_modules -path '*/react-native/Libraries/Animated/Animated.d.ts' -print -quit)"
test -n "$decl_file"

rg -n 'class Animated(Addition|Multiplication)<|export function (add|multiply)' "$decl_file"

files=(
  apps/common-app/src/legacy/recipes/panAndScroll/index.tsx
  apps/common-app/src/legacy/recipes/scaleAndRotate/index.tsx
  apps/common-app/src/legacy/release_tests/nestedFling/index.tsx
  apps/common-app/src/legacy/showcase/bottomSheet/index.tsx
)

if rg -n 'Animated\.Animated(Addition|Multiplication)([[:space:];,)}]|$)' "${files[@]}"; then
  echo "Found an Animated operator type without a type argument"
  exit 1
fi

npx tsc --noEmit -p apps/common-app/tsconfig.json

Repository: software-mansion/react-native-gesture-handler

Length of output: 231


🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

printf '%s\n' '--- candidate files ---'
for f in \
  apps/common-app/src/legacy/recipes/panAndScroll/index.tsx \
  apps/common-app/src/legacy/recipes/scaleAndRotate/index.tsx \
  apps/common-app/src/legacy/release_tests/nestedFling/index.tsx \
  apps/common-app/src/legacy/showcase/bottomSheet/index.tsx
do
  printf '\n--- %s ---\n' "$f"
  sed -n '1,140p' "$f"
done

printf '\n--- React Native version declarations ---\n'
rg -n '"react-native"|"reactNative"|0\.87\.0|AnimatedAddition|AnimatedMultiplication|AnimatedInterpolation' \
  package.json yarn.lock package-lock.json pnpm-lock.yaml apps packages 2>/dev/null | head -250

Repository: software-mansion/react-native-gesture-handler

Length of output: 32655


🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

url='https://raw.githubusercontent.com/facebook/react-native/v0.87.0-rc.3/packages/react-native/Libraries/Animated/Animated.d.ts'
tmp="$(mktemp)"
trap 'rm -f "$tmp"' EXIT
curl --fail --silent --show-error "$url" -o "$tmp"

printf '%s\n' '--- Animated declarations ---'
rg -n -A12 -B4 \
  'class Animated(Addition|Multiplication)|function (add|multiply)|interpolate\(' \
  "$tmp"

printf '%s\n' '--- all Animated operator declarations in the pinned release ---'
rg -n 'Animated(Addition|Multiplication|Interpolation)|add<|multiply<' "$tmp"

Repository: software-mansion/react-native-gesture-handler

Length of output: 3148


🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

url='https://raw.githubusercontent.com/facebook/react-native/v0.87.0-rc.3/packages/react-native/Libraries/Animated/Animated.d.ts'
tmp="$(mktemp)"
trap 'rm -f "$tmp"' EXIT
curl --fail --silent --show-error "$url" -o "$tmp"

printf '%s\n' '--- relevant base and interpolation declarations ---'
sed -n '70,220p' "$tmp"

printf '%s\n' '--- repository TypeScript tooling ---'
command -v tsc || true
command -v npx || true
tsc --version 2>/dev/null || true

Repository: software-mansion/react-native-gesture-handler

Length of output: 5040


🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

probe="$(mktemp --suffix=.ts)"
trap 'rm -f "$probe" "$probe.current.log" "$probe.fixed.log"' EXIT

cat >"$probe" <<'TS'
declare namespace Animated {
  class AnimatedWithChildren {}
  class AnimatedInterpolation<OutputT extends number | string>
    extends AnimatedWithChildren {}
  class AnimatedAddition<OutputT extends number | string>
    extends AnimatedInterpolation<OutputT> {}
  class AnimatedMultiplication<OutputT extends number | string>
    extends AnimatedInterpolation<OutputT> {}
  class Value extends AnimatedWithChildren {
    interpolate<OutputT extends number | string>(
      config: { outputRange: number[] | string[] }
    ): AnimatedInterpolation<OutputT>;
  }
  function add<OutputT extends number | string>(
    a: AnimatedWithChildren,
    b: AnimatedWithChildren
  ): AnimatedAddition<OutputT>;
  function multiply<OutputT extends number | string>(
    a: AnimatedWithChildren,
    b: AnimatedWithChildren
  ): AnimatedMultiplication<OutputT>;
}

declare const value: Animated.Value;

class Current {
  private translateX: Animated.AnimatedAddition;
  private scale: Animated.AnimatedMultiplication;
  private tiltStr: Animated.AnimatedMultiplication;
  private nestedTranslateX: Animated.AnimatedAddition;
  private reverseLastScrollY: Animated.AnimatedMultiplication;

  constructor() {
    this.translateX = Animated.add(value, value);
    this.scale = Animated.multiply(value, value);
    this.tiltStr = value.interpolate({ outputRange: ['0deg', '1deg'] });
    this.nestedTranslateX = Animated.add(value, value);
    this.reverseLastScrollY = Animated.multiply(value, value);
  }
}

class Fixed {
  private translateX: Animated.AnimatedAddition<number>;
  private scale: Animated.AnimatedMultiplication<number>;
  private tiltStr: Animated.AnimatedInterpolation<string>;
  private nestedTranslateX: Animated.AnimatedAddition<number>;
  private reverseLastScrollY: Animated.AnimatedMultiplication<number>;

  constructor() {
    this.translateX = Animated.add(value, value);
    this.scale = Animated.multiply(value, value);
    this.tiltStr = value.interpolate({ outputRange: ['0deg', '1deg'] });
    this.nestedTranslateX = Animated.add(value, value);
    this.reverseLastScrollY = Animated.multiply(value, value);
  }
}
TS

printf '%s\n' '--- current annotations ---'
if tsc --noEmit --strict --skipLibCheck "$probe" >"$probe.current.log" 2>&1; then
  echo 'current probe passed unexpectedly'
else
  sed -n '1,120p' "$probe.current.log"
fi

sed -i '/class Current/,/^}/d' "$probe"

printf '%s\n' '--- proposed annotations ---'
tsc --noEmit --strict --skipLibCheck "$probe"

Repository: software-mansion/react-native-gesture-handler

Length of output: 524


🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

tmpdir="$(mktemp -d)"
trap 'rm -rf "$tmpdir"' EXIT
probe="$tmpdir/probe.ts"

cat >"$probe" <<'TS'
declare namespace Animated {
  class AnimatedWithChildren {}
  class AnimatedInterpolation<OutputT extends number | string>
    extends AnimatedWithChildren {}
  class AnimatedAddition<OutputT extends number | string>
    extends AnimatedInterpolation<OutputT> {}
  class AnimatedMultiplication<OutputT extends number | string>
    extends AnimatedInterpolation<OutputT> {}
  class Value extends AnimatedWithChildren {
    interpolate<OutputT extends number | string>(
      config: { outputRange: number[] | string[] }
    ): AnimatedInterpolation<OutputT>;
  }
  function add<OutputT extends number | string>(
    a: AnimatedWithChildren,
    b: AnimatedWithChildren
  ): AnimatedAddition<OutputT>;
  function multiply<OutputT extends number | string>(
    a: AnimatedWithChildren,
    b: AnimatedWithChildren
  ): AnimatedMultiplication<OutputT>;
}

declare const value: Animated.Value;

class Current {
  private translateX: Animated.AnimatedAddition;
  private scale: Animated.AnimatedMultiplication;
  private tiltStr: Animated.AnimatedMultiplication;
  private nestedTranslateX: Animated.AnimatedAddition;
  private reverseLastScrollY: Animated.AnimatedMultiplication;

  constructor() {
    this.translateX = Animated.add(value, value);
    this.scale = Animated.multiply(value, value);
    this.tiltStr = value.interpolate({ outputRange: ['0deg', '1deg'] });
    this.nestedTranslateX = Animated.add(value, value);
    this.reverseLastScrollY = Animated.multiply(value, value);
  }
}

class Fixed {
  private translateX: Animated.AnimatedAddition<number>;
  private scale: Animated.AnimatedMultiplication<number>;
  private tiltStr: Animated.AnimatedInterpolation<string>;
  private nestedTranslateX: Animated.AnimatedAddition<number>;
  private reverseLastScrollY: Animated.AnimatedMultiplication<number>;

  constructor() {
    this.translateX = Animated.add(value, value);
    this.scale = Animated.multiply(value, value);
    this.tiltStr = value.interpolate({ outputRange: ['0deg', '1deg'] });
    this.nestedTranslateX = Animated.add(value, value);
    this.nestedTranslateX = Animated.add(value, value);
    this.reverseLastScrollY = Animated.multiply(value, value);
  }
}
TS

printf '%s\n' '--- current annotations ---'
if tsc --ignoreConfig --noEmit --strict --skipLibCheck "$probe" >"$tmpdir/current.log" 2>&1; then
  echo 'current probe passed unexpectedly'
else
  sed -n '1,120p' "$tmpdir/current.log"
fi

python3 - "$probe" <<'PY'
from pathlib import Path
p = Path(__import__("sys").argv[1])
s = p.read_text()
start = s.index("class Current {")
end = s.index("class Fixed {")
p.write_text(s[:start] + s[end:])
PY

printf '%s\n' '--- proposed annotations ---'
tsc --ignoreConfig --noEmit --strict --skipLibCheck "$probe"

Repository: software-mansion/react-native-gesture-handler

Length of output: 903


Restore the required React Native Animated type arguments.

React Native 0.87.0-rc.3 reports TS2314 for these declarations. Use Animated.AnimatedAddition<number> for both translateX fields, Animated.AnimatedMultiplication<number> for scale and reverseLastScrollY, and Animated.AnimatedInterpolation<string> for tiltStr.

📍 Affects 4 files
  • apps/common-app/src/legacy/recipes/panAndScroll/index.tsx#L27-L27 (this comment)
  • apps/common-app/src/legacy/recipes/scaleAndRotate/index.tsx#L26-L26
  • apps/common-app/src/legacy/recipes/scaleAndRotate/index.tsx#L36-L36
  • apps/common-app/src/legacy/release_tests/nestedFling/index.tsx#L17-L17
  • apps/common-app/src/legacy/showcase/bottomSheet/index.tsx#L41-L41
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/common-app/src/legacy/recipes/panAndScroll/index.tsx` at line 27,
Restore the required React Native Animated generic arguments across the affected
declarations: in apps/common-app/src/legacy/recipes/panAndScroll/index.tsx:27
and apps/common-app/src/legacy/recipes/scaleAndRotate/index.tsx:26, use
Animated.AnimatedAddition<number> for both translateX fields; in
apps/common-app/src/legacy/recipes/scaleAndRotate/index.tsx:36 and
apps/common-app/src/legacy/release_tests/nestedFling/index.tsx:17, use
Animated.AnimatedMultiplication<number> for scale and reverseLastScrollY; and in
apps/common-app/src/legacy/showcase/bottomSheet/index.tsx:41, use
Animated.AnimatedInterpolation<string> for tiltStr.

Comment on lines +138 to +140
ref={(node: React.ComponentRef<typeof RNScroll> | null) =>
(this.scrollView = node)
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

rg -n -C 3 'this\.scrollView = node|ref=\{' \
  apps/common-app/src/legacy/release_tests/combo/index.tsx

npx tsc --noEmit -p apps/common-app/tsconfig.json

Repository: software-mansion/react-native-gesture-handler

Length of output: 50402


🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

printf '%s\n' '--- combo source ---'
sed -n '1,180p' apps/common-app/src/legacy/release_tests/combo/index.tsx

printf '%s\n' '--- relevant symbols and ref types ---'
rg -n -C 4 'RNScroll|GHScroll|ScrollViewComponent|scrollView' \
  apps/common-app/src/legacy/release_tests/combo/index.tsx

printf '%s\n' '--- React and TypeScript versions/configuration ---'
rg -n '"(react|`@types/react`|typescript)"|react-19|RefCallback' \
  package.json yarn.lock package-lock.json pnpm-lock.yaml apps/common-app 2>/dev/null | head -200

Repository: software-mansion/react-native-gesture-handler

Length of output: 9647


🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

sed -n '1,180p' apps/common-app/src/legacy/release_tests/combo/index.tsx
rg -n -C 5 'RNScroll|GHScroll|ScrollViewComponent|scrollView' \
  apps/common-app/src/legacy/release_tests/combo/index.tsx
rg -n '"(react|`@types/react`|typescript)"|RefCallback' \
  package.json yarn.lock apps/common-app 2>/dev/null | head -200

Repository: software-mansion/react-native-gesture-handler

Length of output: 9706


🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

printf '%s\n' '--- LegacyScrollView definitions ---'
rg -n -C 8 'LegacyScrollView|class.*ScrollView|scrollTo' \
  packages apps 2>/dev/null | head -240

printf '%s\n' '--- React RefCallback declarations in repository data ---'
rg -n -C 5 'type RefCallback|interface RefCallback|RefCallback<' \
  . --glob '*.d.ts' --glob 'package.json' --glob 'yarn.lock' 2>/dev/null | head -160

printf '%s\n' '--- installed declaration availability ---'
for f in node_modules/@types/react/index.d.ts apps/common-app/node_modules/@types/react/index.d.ts; do
  if [ -f "$f" ]; then
    echo "$f"
    rg -n -C 5 'type RefCallback|interface RefCallback|type ComponentRef' "$f" | head -100
  fi
done

Repository: software-mansion/react-native-gesture-handler

Length of output: 23559


🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

rg -n -C 8 'LegacyScrollView|class.*ScrollView|scrollTo' \
  packages apps 2>/dev/null | head -240
rg -n -C 5 'type RefCallback|interface RefCallback|RefCallback<' \
  . --glob '*.d.ts' --glob 'package.json' --glob 'yarn.lock' 2>/dev/null | head -160
for f in node_modules/@types/react/index.d.ts apps/common-app/node_modules/@types/react/index.d.ts; do
  if [ -f "$f" ]; then
    echo "$f"
    rg -n -C 5 'type RefCallback|interface RefCallback|type ComponentRef' "$f" | head -100
  fi
done

Repository: software-mansion/react-native-gesture-handler

Length of output: 25019


🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

python3 - <<'PY'
from pathlib import Path
p = Path("packages/react-native-gesture-handler/src/components/GestureComponents.tsx")
text = p.read_text()
required = [
    "ref?: React.Ref<React.ComponentRef<typeof RNScrollView> | null>;",
    "export type LegacyScrollView = typeof GHScrollView &",
    "React.ComponentRef<typeof RNScrollView>;",
]
for item in required:
    print(f"{item!r}: {item in text}")
print("callback assignment returns a value: true")
print("callback block-body replacement returns a value: false")
PY

Repository: software-mansion/react-native-gesture-handler

Length of output: 463


🌐 Web query:

React 19 upgrade guide ref callback cleanup function TypeScript non-function return value react.dev

💡 Result:

In React 19, you can return a cleanup function from a ref callback [1][2][3]. When the component unmounts or the ref changes, React will execute this cleanup function instead of calling the ref callback with null [1][2]. TypeScript Update and Non-Function Return Values Because React 19 now interprets a returned function from a ref callback as a cleanup function, TypeScript will reject any ref callback that returns a non-function value (e.g., an HTML element or any other object) [1][2][4]. This prevents ambiguity between returning a cleanup function and returning a value that might have been accidentally returned from an implicit return in an arrow function [1][4]. How to Fix TypeScript Errors If your ref callback previously relied on an implicit return (often for one-line logic), you must refactor it to ensure it does not return any value [1][2][4]. Before (Implicit return, causes TypeScript error): ref={node => (myRef.current = node)} After (Explicit block, no return value): ref={node => { myRef.current = node; }} You can automate this migration by running the following codemod: npx types-react-codemod no-implicit-ref-callback-return src/ [1][5][4] For further details on this pattern, refer to the React 19 documentation on ref callbacks [1][3].

Citations:


🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

python3 - <<'PY'
from pathlib import Path
p = Path("packages/react-native-gesture-handler/src/components/GestureComponents.tsx")
text = p.read_text()
for item in [
    "ref?: React.Ref<React.ComponentRef<typeof RNScrollView> | null>;",
    "export type LegacyScrollView = typeof GHScrollView &",
    "React.ComponentRef<typeof RNScrollView>;",
]:
    print(f"{item!r}: {item in text}")
PY

Repository: software-mansion/react-native-gesture-handler

Length of output: 366


Use a block body for the ref callback. React 19 types reject the value returned by this.scrollView = node; the callback must return nothing.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/common-app/src/legacy/release_tests/combo/index.tsx` around lines 138 -
140, Update the ref callback assigning this.scrollView to use a block body,
ensuring the assignment executes without implicitly returning its value and the
callback returns nothing.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

New React Native version support This pull request aims to bring support for new React Native version

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants