Description
Is there an existing issue for this?
- I have searched the existing issues
Describe the issue
A memory leak has been identified in the Animated API for react-native-web, specifically in the AnimatedWithChildren class and related animations. The issue arises when rendering multiple animated components using transforms and interpolation. Over time, internal arrays (like children in AnimatedWithChildren) grow indefinitely, leading to increased memory usage and performance degradation.
Expected behavior
-
Memory usage should remain stable over time as animations run and re-renders occur, regardless of the number of Animated components rendered.
-
Animated values (Animated.Value, Animated.ValueXY, etc.) should not accumulate indefinitely in the internal array of AnimatedWithChildren.
-
The AnimatedWithChildren class or related classes should ensure that duplicate or unused children are not repeatedly added to its array.
-
Animations should not degrade in performance as time progresses, even with multiple animated components and transformations (e.g., transform styles with interpolated values).
Steps to reproduce
- Implement a component or file that periodically renders multiple Animated elements using transform and interpolate, as these trigger the issue more frequently.
- Monitor performance degradation visually as animations run.
- Use tools like Chrome DevTools to confirm memory growth over time, by taking heap snapshots.
Package Versions
- Expo: ~52.0.27
- React Native: 0.76.6
- React Native Web: ^0.19.13
- React: 18.3.1
- React DOM: 18.3.1
Test case
https://codesandbox.io/p/sandbox/animated-memory-leak-7j5d2n
Additional comments
CodeSandbox isn’t the most reliable environment for demonstrating memory growth due to other objects accumulating during runtime. Instead, I’ve hosted a reproduction of the issue on Netlify here:
You’ll notice performance degradation within approximately 10 seconds, depending on your machine. Using Chrome DevTools or similar tools, you can observe memory snapshots that reveal the growth of Animated elements in the Array constructor.
Example Code
Below is the code used to reproduce the issue. You can implement this yourself to observe the problem:
import React, { useState, useEffect, useMemo } from 'react';
import { Animated, View, StyleSheet } from 'react-native';
const COLORS = ['tomato', 'skyblue', 'gold', 'limegreen', 'orchid'];
export default function App() {
const [counter, setCounter] = useState(0);
const animatedValues = useMemo(
() => Array.from({ length: 500 }, () => new Animated.Value(0)),
[]
);
useEffect(() => {
const interval = setInterval(() => setCounter((prev) => prev + 1), 15);
return () => clearInterval(interval);
}, []);
useEffect(() => {
animatedValues.forEach((animatedValue) => {
Animated.loop(
Animated.sequence([
Animated.timing(animatedValue, {
toValue: 1,
duration: 1000,
useNativeDriver: false,
}),
Animated.timing(animatedValue, {
toValue: 0,
duration: 1000,
useNativeDriver: false,
}),
])
).start();
});
}, [animatedValues]);
const squares = animatedValues.map((animatedValue, index) => (
<Animated.View
key={index}
style={[
styles.box,
{ backgroundColor: COLORS[index % COLORS.length] },
{
transform: [
{
translateX: animatedValue.interpolate({
inputRange: [0, 1],
outputRange: [0, 100 * (index % 2 === 0 ? 1 : -1)],
}),
},
],
},
]}
/>
));
return <View style={styles.container}>{squares}</View>;
}
const styles = StyleSheet.create({
container: {
flex: 1,
flexWrap: 'wrap',
flexDirection: 'row',
justifyContent: 'center',
alignItems: 'center',
},
box: {
width: 50,
height: 50,
margin: 10,
},
});
Temporary Fix
A potential short-term fix involves modifying the addChild method in AnimatedWithChildren to avoid redundant additions to the children array:
addChild(child) {
if (!this.children.includes(child)) {
this.children.push(child);
}
}
This change eliminates memory growth, but it seems like a hack.