Skip to content

Memory Leak in Animated API with AnimatedWithChildren #2756

Closed
@c-miles

Description

@c-miles

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions