Memory Leaks when component is disposed
Environment
System: OS: macOS 13.2.1 CPU: (8) x64 Intel(R) Core(TM) i7-7820HQ CPU @ 2.90GHz Memory: 16.34 MB / 16.00 GB Shell: 3.2.57 - /bin/bash Binaries: Node: 16.17.1 - ~/.nvm/versions/node/v16.17.1/bin/node Yarn: 1.17.3 - /usr/local/bin/yarn npm: 8.15.0 - ~/.nvm/versions/node/v16.17.1/bin/npm Watchman: 4.9.0 - /usr/local/bin/watchman Managers: CocoaPods: 1.11.2 - /usr/local/bin/pod SDKs: iOS SDK: Platforms: DriverKit 22.2, iOS 16.2, macOS 13.1, tvOS 16.1, watchOS 9.1 Android SDK: Not Found IDEs: Android Studio: 3.4 AI-183.6156.11.34.5522156 Xcode: 14.2/14C18 - /usr/bin/xcodebuild Languages: Java: Not Found npmPackages: @react-native-community/cli: Not Found react: 18.2.0 => 18.2.0 react-native: 0.71.3 => 0.71.3 react-native-macos: Not Found npmGlobalPackages: react-native: Not Found
Description
When using pager view with larger resources, memory seems to leak by the amount contained in the active page. Originally discovered in a project using a routing library but for this example the toggle component simulates a page disposal. As resources are proportionally small for the example to reproduce press the toggle button multiple times Although the leaking memory from this example is small It has been able to reach over 8gb where images have client side processing applied such as grayscale.
Memory Profile with pager view included

Memory Profile with pager view excluded
Reproducible Demo
import React, { useMemo, useState, useCallback } from 'react';
import {
View,
Button,
StyleSheet,
SafeAreaView,
} from 'react-native';
import FastImage from 'react-native-fast-image'
import PagerView from 'react-native-pager-view';
const data = new Array(100).fill(1)
function App() {
const [visible, setVisible] = useState(true)
const onPress = useCallback(() => {
setVisible((x) => !x)
}, [])
return (
<SafeAreaView style={{ flex: 1}}>
<View style={{ flex: 0 }}>
<Button title={'Toggle'} onPress={onPress} />
</View>
<Toggle visible={visible}>
<Pager />
</Toggle>
</SafeAreaView>
);
};
function Toggle({ visible, children }) {
return visible
? children
: null
}
function Pager() {
const content = useMemo(() => {
return data.map((x, i) => {
return <Image value={i} key={i} />
})
}, [data])
return (
// Apply comment to the below line to demo memory is stable without this component
<PagerView style={styles.pagerView} initialPage={0}>
<View key="1">
{content}
</View>
</PagerView>
)
}
function Image({ value }) {
const source = useMemo(() => {
const i = value % 40
return { uri: `https://unsplash.it/1200/1200?image=${i}` }
}, [value])
return (
<FastImage
style={styles.image}
source={source}
resizeMode={FastImage.resizeMode.cover}
/>
)
}
const styles = StyleSheet.create({
pagerView: {
flex: 1,
},
image: {
margin: 8,
aspectRatio: 1,
width: 400,
height: 400
}
});
export default App
Hello, which version of pager-view did you test this on?
@okwasniewski
Originally discovered with 5.4.9 but persisted after upgrading to 6.1.4
Also seems to persist in fresh projects with 6.1.0-rc.2
Just change 'NSHashTableStrongMemory' to 'NSHashTableWeakMemory' in file ReactNativePageView.m.
The item view outside the window can then be freed. And memory can be easily stabilized.
Use this patch react-native-pager-view+5.4.25.patch as below
diff --git a/node_modules/react-native-pager-view/ios/ReactNativePageView.m b/node_modules/react-native-pager-view/ios/ReactNativePageView.m
index eacfbe8..c61745f 100644
--- a/node_modules/react-native-pager-view/ios/ReactNativePageView.m
+++ b/node_modules/react-native-pager-view/ios/ReactNativePageView.m
@@ -44,7 +44,7 @@ - (instancetype)initWithEventDispatcher:(RCTEventDispatcher *)eventDispatcher {
_dismissKeyboard = UIScrollViewKeyboardDismissModeNone;
_coalescingKey = 0;
_eventDispatcher = eventDispatcher;
- _cachedControllers = [NSHashTable hashTableWithOptions:NSHashTableStrongMemory];
+ _cachedControllers = [NSHashTable hashTableWithOptions:NSHashTableWeakMemory];
_overdrag = NO;
_layoutDirection = @"ltr";
}
I face the same problem exactly. My pager view version is 6.2.3
- Memory leak for active pages
- I display web views each tabs => massive memory leak
@zyestin hi, have you used this patch in production? is it stable? Just found about this issue, just checking in.
https://github.com/callstack/react-native-pager-view/pull/1040#issuecomment-3538503014
diff --git a/node_modules/react-native-pager-view/android/src/main/java/com/reactnativepagerview/NestedScrollableHost.kt b/node_modules/react-native-pager-view/android/src/main/java/com/reactnativepagerview/NestedScrollableHost.kt
index 87b58d0f..e9d0ace1 100644
--- a/node_modules/react-native-pager-view/android/src/main/java/com/reactnativepagerview/NestedScrollableHost.kt
+++ b/node_modules/react-native-pager-view/android/src/main/java/com/reactnativepagerview/NestedScrollableHost.kt
@@ -25,6 +25,7 @@ class NestedScrollableHost : FrameLayout {
constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)
public var initialIndex: Int? = null
public var didSetInitialIndex = false
+ public var pageChangeCallback: ViewPager2.OnPageChangeCallback? = null
private var touchSlop = 0
private var initialX = 0f
private var initialY = 0f
diff --git a/node_modules/react-native-pager-view/android/src/main/java/com/reactnativepagerview/PagerViewViewManager.kt b/node_modules/react-native-pager-view/android/src/main/java/com/reactnativepagerview/PagerViewViewManager.kt
index 8ec286a7..19f46363 100644
--- a/node_modules/react-native-pager-view/android/src/main/java/com/reactnativepagerview/PagerViewViewManager.kt
+++ b/node_modules/react-native-pager-view/android/src/main/java/com/reactnativepagerview/PagerViewViewManager.kt
@@ -52,7 +52,7 @@ class PagerViewViewManager : ViewGroupManager<NestedScrollableHost>(), RNCViewPa
vp.isSaveEnabled = false
vp.post {
- vp.registerOnPageChangeCallback(object : OnPageChangeCallback() {
+ val callback = object : OnPageChangeCallback() {
override fun onPageScrolled(position: Int, positionOffset: Float, positionOffsetPixels: Int) {
super.onPageScrolled(position, positionOffset, positionOffsetPixels)
UIManagerHelper.getEventDispatcherForReactTag(reactContext, host.id)?.dispatchEvent(
@@ -79,7 +79,9 @@ class PagerViewViewManager : ViewGroupManager<NestedScrollableHost>(), RNCViewPa
PageScrollStateChangedEvent(host.id, pageScrollState)
)
}
- })
+ }
+ host.pageChangeCallback = callback
+ vp.registerOnPageChangeCallback(callback)
UIManagerHelper.getEventDispatcherForReactTag(reactContext, host.id)?.dispatchEvent(
PageSelectedEvent(host.id, vp.currentItem)
)
@@ -200,6 +202,20 @@ class PagerViewViewManager : ViewGroupManager<NestedScrollableHost>(), RNCViewPa
}
}
+ override fun onDropViewInstance(view: NestedScrollableHost) {
+ // Unregister the page change callback to prevent memory leaks
+ val viewPager = PagerViewViewManagerImpl.getViewPager(view)
+ view.pageChangeCallback?.let { callback ->
+ viewPager.unregisterOnPageChangeCallback(callback)
+ view.pageChangeCallback = null
+ }
+
+ // Clear the adapter to release references to child views
+ viewPager.adapter = null
+
+ super.onDropViewInstance(view)
+ }
+
override fun getExportedCustomDirectEventTypeConstants(): MutableMap<String, Map<String, String>> {
return MapBuilder.of(
PageScrollEvent.EVENT_NAME, MapBuilder.of("registrationName", "onPageScroll"),
diff --git a/node_modules/react-native-pager-view/android/src/main/java/com/reactnativepagerview/ViewPagerAdapter.kt b/node_modules/react-native-pager-view/android/src/main/java/com/reactnativepagerview/ViewPagerAdapter.kt
index fd3530e1..8aab5f66 100644
--- a/node_modules/react-native-pager-view/android/src/main/java/com/reactnativepagerview/ViewPagerAdapter.kt
+++ b/node_modules/react-native-pager-view/android/src/main/java/com/reactnativepagerview/ViewPagerAdapter.kt
@@ -30,6 +30,12 @@ class ViewPagerAdapter() : Adapter<ViewPagerViewHolder>() {
container.addView(child)
}
+ override fun onViewRecycled(holder: ViewPagerViewHolder) {
+ super.onViewRecycled(holder)
+ // Clean up the holder's container to prevent memory leaks
+ holder.container.removeAllViews()
+ }
+
override fun getItemCount(): Int {
return childrenViews.size
}
diff --git a/node_modules/react-native-pager-view/ios/ReactNativePageView.m b/node_modules/react-native-pager-view/ios/ReactNativePageView.m
index eacfbe8..c61745f 100644
--- a/node_modules/react-native-pager-view/ios/ReactNativePageView.m
+++ b/node_modules/react-native-pager-view/ios/ReactNativePageView.m
@@ -44,7 +44,7 @@ - (instancetype)initWithEventDispatcher:(RCTEventDispatcher *)eventDispatcher {
_dismissKeyboard = UIScrollViewKeyboardDismissModeNone;
_coalescingKey = 0;
_eventDispatcher = eventDispatcher;
- _cachedControllers = [NSHashTable hashTableWithOptions:NSHashTableStrongMemory];
+ _cachedControllers = [NSHashTable hashTableWithOptions:NSHashTableWeakMemory];
_overdrag = NO;
_layoutDirection = @"ltr";
}