Bump to React Native 0.87 - #4310
Conversation
There was a problem hiding this comment.
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-exampleTypeScript configuration for TS 6 (and align tsconfigtypeswith other apps). - Refresh
basic-examplenative build tooling: Gradle wrapper/SDK levels, Gradle properties, and iOSPodfile.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.
|
@coderabbitai review |
✅ Action performedReview finished.
|
📝 WalkthroughWalkthroughThe 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. ChangesReact Native 0.87 compatibility
Estimated code review effort: 2 (Simple) | ~10 minutes 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches 💡 1📝 Generate docstrings 💡
🧪 Generate unit tests (beta)
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
ESLint install failed. For unrecoverable errors, disable the tool in CodeRabbit configuration. Comment |
There was a problem hiding this comment.
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
⛔ Files ignored due to path filters (3)
apps/basic-example/android/gradle/wrapper/gradle-wrapper.jaris excluded by!**/*.jarapps/basic-example/ios/Podfile.lockis excluded by!**/*.lockyarn.lockis excluded by!**/yarn.lock,!**/*.lock
📒 Files selected for processing (22)
apps/basic-example/android/app/build.gradleapps/basic-example/android/build.gradleapps/basic-example/android/gradle.propertiesapps/basic-example/android/gradle/wrapper/gradle-wrapper.propertiesapps/basic-example/android/gradlewapps/basic-example/ios/BasicExample/PrivacyInfo.xcprivacyapps/basic-example/package.jsonapps/basic-example/src/Navigator.tsxapps/basic-example/tsconfig.jsonapps/common-app/src/ListWithHeader/ListWithHeader.tsxapps/common-app/src/legacy/recipes/panAndScroll/index.tsxapps/common-app/src/legacy/recipes/scaleAndRotate/index.tsxapps/common-app/src/legacy/release_tests/combo/index.tsxapps/common-app/src/legacy/release_tests/nestedFling/index.tsxapps/common-app/src/legacy/release_tests/touchables/index.tsxapps/common-app/src/legacy/showcase/bottomSheet/index.tsxapps/common-app/src/legacy/v2_api/calculator/index.tsxapps/common-app/src/new_api/components/scrollview/index.tsxapps/common-app/tsconfig.jsonpackages/react-native-gesture-handler/package.jsonpackages/react-native-gesture-handler/src/components/ReanimatedDrawerLayout.tsxpackages/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 |
There was a problem hiding this comment.
🎯 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 -200Repository: 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:
- 1: https://developer.android.com/develop/ui/views/layout/edge-to-edge
- 2: [0.81] Android 16 changes impacting React Native react-native-community/discussions-and-proposals#921
- 3: https://reactnavigation.org/docs/handling-safe-area/
- 4: https://github.com/zoontek/react-native-edge-to-edge?tab=readme-ov-file
- 5: https://reactnavigation.org/docs/8.x/handling-safe-area/
- 6: Handling Android 15's edge-to-edge enforcement on React Native react-native-community/discussions-and-proposals#827
- 7: https://docs.expo.dev/versions/latest/sdk/safe-area-context/
- 8: https://docs.expo.dev/develop/user-interface/safe-areas
- 9: Is this library still useful in React Native 0.81+? zoontek/react-native-edge-to-edge#96
- 10: SafeAreaView content overlaps status bar & edge-to-edge not working on Android 15+ appandflow/react-native-safe-area-context#633
🏁 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 || trueRepository: 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:
- 1: https://reactnative.dev/docs/next/safeareaview
- 2: https://reactnative.dev/docs/0.81/safeareaview
- 3: https://reactnative.dev/docs/0.83/safeareaview
- 4: https://reactnative.dev/docs/navigation
- 5: https://reactnative.dev/docs/safeareaview
- 6: https://reactnative.dev/docs/0.85/safeareaview
🌐 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:
- 1: Deprecating SafeAreaView react/react-native#51896
- 2: Edge-to-Edge Rendering Issue on Android 15 in React Native react/react-native#50423
- 3: Add edge-to-edge opt-in support react/react-native#52088
- 4: Fix Dimensions window values on Android < 15 react/react-native#47554
- 5: react/react-native@c2280e3
- 6: https://github.com/facebook/react-native/releases/tag/v0.86.0
🏁 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 -240Repository: 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; |
There was a problem hiding this comment.
🎯 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.jsonRepository: 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 -250Repository: 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 || trueRepository: 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-L26apps/common-app/src/legacy/recipes/scaleAndRotate/index.tsx#L36-L36apps/common-app/src/legacy/release_tests/nestedFling/index.tsx#L17-L17apps/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.
| ref={(node: React.ComponentRef<typeof RNScroll> | null) => | ||
| (this.scrollView = node) | ||
| } |
There was a problem hiding this comment.
🎯 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.jsonRepository: 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 -200Repository: 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 -200Repository: 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
doneRepository: 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
doneRepository: 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")
PYRepository: 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:
- 1: https://react.dev/blog/2024/12/05/react-19
- 2: https://blog.saeloun.com/2025/03/24/react-19-ref-as-prop/
- 3: https://react.dev/learn/manipulating-the-dom-with-refs
- 4: https://react.dev/blog/2024/04/25/react-19-upgrade-guide
- 5: [@types/react] Version 18.2.69 and Above: Void Problem with useRef Callback DefinitelyTyped/DefinitelyTyped#69416
🏁 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}")
PYRepository: 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.
Description
This PR brings support for React Native 0.87
Status
0.87.0-rc.3 ✅
Required changes
Test plan
Tested that
basic-examplebuilds and works correctly.Summary by CodeRabbit
New Features
Improvements
Bug Fixes
Maintenance