Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Memory Leak in Animated API with AnimatedWithChildren #2756

Closed
1 task done
c-miles opened this issue Jan 21, 2025 · 2 comments
Closed
1 task done

Memory Leak in Animated API with AnimatedWithChildren #2756

c-miles opened this issue Jan 21, 2025 · 2 comments
Labels

Comments

@c-miles
Copy link

c-miles commented Jan 21, 2025

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

  1. Implement a component or file that periodically renders multiple Animated elements using transform and interpolate, as these trigger the issue more frequently.
  2. Monitor performance degradation visually as animations run.
  3. 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:

👉 Live Demo

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.

@c-miles c-miles added the bug label Jan 21, 2025
@necolas
Copy link
Owner

necolas commented Jan 22, 2025

Animated is maintained in React Native and the code was once copy-pasted into the "vendor" directory here. It hasn't been updated in a while and the goal was to be able to consume it as a package dependency (published by React Native). See facebook/react-native#35263

The issue you're reporting may or may not exist in the current version of Animated in React Native.

@c-miles
Copy link
Author

c-miles commented Jan 22, 2025

@necolas

Appreciate the timely response. I've opened an issue in React Native.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

2 participants