After implementing a swipe gesture on list items inspired by SwipeableListExample.js I noticed a memory leak in my app. I've been able to reproduce with minimal changes to SwipeableListExample.js in Reanimated example project.
It seems that calling a function declared in the same component as the worklet calling it, is creating a memory leak.
Maybe I'm misusing the library but I haven't found any reference to that in the documentation so I'm assuming this is a bug.
SwipeableListExample.js in the Example projectuseState() in the ListItem component.useAnimatedGestureHandler worklet.Previous PanGestureHandler components and their children should be deallocated when their parent is deallocated or no longer attached to any parent. This is the behavior of the example project without modifications.
All PanGestureHandler components and their children stay in memory until application is killed.
Safari Dev Tools, list of new PanGestureHandler in memory since initial snapshot.

Detail of one retained PanGestureHandler

Edited part of SwipeableList.js (see comments):
function ListItem({ item, onRemove }) {
const isRemoving = useSharedValue(false);
const translateX = useSharedValue(0);
// Declare stateful value
const [isSwiping, setIsSwiping] = useState(false);
const handler = useAnimatedGestureHandler({
onStart: (evt, ctx) => {
ctx.startX = translateX.value;
// Modify stateful value from worklet
runOnJS(setIsSwiping)(true);
},
onActive: (evt, ctx) => {
const nextTranslate = evt.translationX + ctx.startX;
translateX.value = Math.min(0, Math.max(nextTranslate, MAX_TRANSLATE));
},
onEnd: (evt) => {
if (evt.velocityX < -20) {
translateX.value = withSpring(
MAX_TRANSLATE,
springConfig(evt.velocityX)
);
} else {
translateX.value = withSpring(0, springConfig(evt.velocityX));
}
},
});
// ... Rest of ListItem implementation. Truncated for clarity
}
I can provide a project on Github if it helps.
Hello!
Congratulations! Your issue passed the validator! Thank you!
Hello 馃憢 I just want to note that useState doesn't seem to play a role here. It looks like there's a problem but for me, such change in code suffices:
export default function SwipableList() {
function onRemove() {
alert('Removed');
}
for (let i=0;i<1000;++i) {
data.push({ id: `${i + 8}`, title: 'dummy item ' + i });
}
return (
<View style={s.container}>
<FlatList
data={data}
renderItem={({ item }) => <ListItem item={item} onRemove={onRemove} />}
keyExtractor={(item) => item.id}
/>
</View>
);
}
Just creating a lot of dummy elements and then navigating back and forth does the job, the memory increases significantly until the app is reloaded.
I think useState creates a problem here. Actually, capturing setState function does as it has ref to FiberNode and as a result to the PanGestureHandler what creates a reference loop.
Here is a minimal code example I found:
import React, {useState} from 'react';
import { View, Text } from 'react-native';
import Animated, { useAnimatedGestureHandler, runOnJS } from 'react-native-reanimated';
import { PanGestureHandler } from 'react-native-gesture-handler';
export default function App() {
const [state, setState] = useState(false);
const gh = useAnimatedGestureHandler({
onStart:() => {
runOnJS(setState)(true);
},
})
return (
<View style={{flex:1, backgroundColor: 'yellow'}}>
<PanGestureHandler onGestureEvent={gh}>
<Animated.View>
<Text>test</Text>
</Animated.View>
</PanGestureHandler>
</View>
);
}
@Szymon20000 I think you're right, useState is probably not the actual problem here. I managed to reproduce the leak by just declaring a function in the component and using it in the worklet.
Does it mean that all functions used in a worklet must be declared outside the component? Intuitively, I was expecting useAnimatedGestureHandler to behave like any other hook.
@Szymon20000 I think you're right,
useStateis probably not the actual problem here. I managed to reproduce the leak by just declaring a function in the component and using it in the worklet.
Does it mean that all functions used in a worklet must be declared outside the component? Intuitively, I was expectinguseAnimatedGestureHandlerto behave like any other hook.
@sparga I'm confused. First I wrote that useState doesn't play a role here, by what I meant it doesn't cause the problem itself. Then @Szymon20000 wrote that indeed useState is the cause of the problem. Finally, you replied that Szymon's right - useState is not a problem.

Is it just me having a weird day or something doesn't make sense here?
Chiming in for @sparga here since he's on another timezone. Calling setState from the worklet of the AnimatedGestureHandler triggers the leak. The minimal example @Szymon20000 wrote is correct.
I think you can disregard @sparga 's latest comment for now. He'll clarify what he meant later.
Sure, no problem, I just didn't understand 馃槈
Sorry, I should have been clearer, forgot my previous message.
Yes, you're right @karol-bisztyga, useState doesn't play a direct role. I simply meant that using setState in the worklet is what causes the leak.
Here is another example similar to the first one (without any use of any state this time 馃槄):
function ListItem({ item, onRemove }) {
const completeSwipe = () => {
onRemove()
}
// ... Rest of ListItem implementation. Truncated for clarity
}
If at any point in a worklet declared in the ListItem function you call runOnJS(completeSwipe)();, it creates some kind of reference cycle and the PanGestureHandlers and their children leak.
I also tried your code and while memory usage is increasing it looks like a different issue because the PanGestureHandlers are correctly deallocated in that case.
Let me know if it's still unclear. I can provide a sample project if needed. Thank you!
Today I discovered that it could just be a problem of the gesture handler itself. Such code leaks memory:
import React from 'react';
import { View, Text } from 'react-native';
import { PanGestureHandler } from 'react-native-gesture-handler';
export default function App() {
const handler = (e) => {};
return (
<View>
<PanGestureHandler activeOffsetX={[-10, 10]} onGestureEvent={handler}>
<Text>AAA</Text>
</PanGestureHandler>
</View>
);
}
It has nothing to do with reanimated.

We will let you know when we check the GH.
Hey!
All the problems with memory leaks that we detected in reanimated 2 have been solved here
We also noticed that for some cases the leaks don't really happen - the dangling objects wait for the GC on the UI side which works separately from the point of view of that one on the JS side - the information about this is in the PR.
As for the GH: I didn't reproduce it now but when I do I'll create an issue in their repo.
Thanks for bringing this up 馃檶
Thanks @karol-bisztyga 馃檶 ! We'll test this out ASAP!
The release containing this change will come out soon, in the meantime, you can use the daily builds
Most helpful comment
Today I discovered that it could just be a problem of the gesture handler itself. Such code leaks memory:
It has nothing to do with reanimated.
We will let you know when we check the GH.