# How to Pass a Dynamic Array to the React useEffect Dependency Array Without Infinite Loops

> Learn how to pass dynamic arrays to the React useEffect dependency array. Prevent infinite loops by stabilizing array references with useMemo or useRef for efficient updates.

- Repository: [Meta/react](https://github.com/facebook/react)
- Tags: how-to-guide
- Published: 2026-02-16

---

**To pass a dynamic array to `useEffect` without triggering the effect on every render, you must stabilize the array's reference using `useMemo` or `useRef` so that React's shallow equality check only detects changes when the actual contents differ.**

When working with the [facebook/react](https://github.com/facebook/react) codebase, understanding how the dependency array comparison works is essential for avoiding unnecessary effect executions. The `useEffect` hook relies on a shallow equality algorithm to determine whether its callback should run, which means the *reference* identity of an array matters just as much as its contents.

## Why Array References Trigger useEffect Re-runs

React determines whether to execute an effect by comparing the current dependency array with the previous one using a function called **`areHookInputsEqual`** in [`packages/react-reconciler/src/ReactFiberHooks.js`](https://github.com/facebook/react/blob/main/packages/react-reconciler/src/ReactFiberHooks.js) (lines 53-99). This function iterates over both arrays and compares each entry using `Object.is` equality.

If you create a new array literal on every render—such as `[item1, item2]`—the reference changes even if the contents are identical. Because `areHookInputsEqual` first checks if the arrays are the same reference (lines 53-60), and then performs shallow comparison, a new array reference forces React to re-run the effect.

## Stabilizing Dynamic Arrays with useMemo

The recommended approach for passing a dynamic array to `useEffect` is to memoize the array using **`useMemo`**. This ensures the array reference remains stable unless the underlying values actually change.

In [`packages/react/src/ReactHooks.js`](https://github.com/facebook/react/blob/main/packages/react/src/ReactHooks.js) (lines 87-101), the `useEffect` implementation forwards the dependency array to the reconciler. By ensuring your array is memoized before it reaches this point, you satisfy the shallow equality check in `areHookInputsEqual`.

```typescript
import { useEffect, useMemo } from 'react';

function DataProcessor({ items }) {
  // Stabilize the array: only recalculate when items change
  const processedIds = useMemo(() => 
    items.map(item => item.id), 
    [items]
  );

  useEffect(() => {
    // This only runs when the actual ids change, not on every render
    console.log('Fetching data for ids:', processedIds);
    fetchData(processedIds);
  }, [processedIds]); // Stable reference thanks to useMemo
}

```

## Using useRef for Imperative Array Management

For scenarios where you need to mutate the array imperatively or avoid the overhead of memoization calculations, **`useRef`** provides a stable container. The `.current` property of a ref maintains the same reference across renders, allowing you to manually control when the array changes.

```typescript
import { useEffect, useRef } from 'react';

function ManualUpdater({ data }) {
  const depsRef = useRef([]);

  // Manually check if contents changed before updating ref
  if (data.length !== depsRef.current.length ||
      data.some((v, i) => v !== depsRef.current[i])) {
    depsRef.current = data.slice(); // Create new array only when needed
  }

  useEffect(() => {
    console.log('Effect triggered by data change');
    processData(depsRef.current);
  }, [depsRef.current]); // Stable until manually updated
}

```

## Avoiding the JSON.stringify Anti-pattern

While you may encounter suggestions to use `JSON.stringify(array)` as a dependency, this approach is **not recommended** for production code. Serialization is computationally expensive and can produce false positives for non-primitive values like functions or class instances.

```typescript
// ❌ Avoid this pattern
useEffect(() => {
  // Effect logic
}, [JSON.stringify(myArray)]); // Expensive and unreliable

```

Instead, rely on `useMemo` to create a stable primitive dependency array derived from your dynamic content.

## Key Implementation Files in React

Understanding the internal mechanics helps explain why reference stability matters:

- **[`packages/react-reconciler/src/ReactFiberHooks.js`](https://github.com/facebook/react/blob/main/packages/react-reconciler/src/ReactFiberHooks.js)** (lines 53-99): Contains `areHookInputsEqual`, the shallow equality function that compares dependency arrays using `Object.is`.
- **[`packages/react/src/ReactHooks.js`](https://github.com/facebook/react/blob/main/packages/react/src/ReactHooks.js)** (lines 87-101): Defines the `useEffect` export that forwards the callback and dependencies to the reconciler's dispatcher.

These implementations confirm that React performs reference and shallow equality checks before deciding to execute an effect.

## Summary

- React compares `useEffect` dependency arrays using shallow equality (`areHookInputsEqual`), checking each entry with `Object.is`.
- Creating new array literals on every render changes the reference and triggers the effect unnecessarily.
- **Use `useMemo`** to stabilize the array reference, recalculating only when underlying values change.
- **Use `useRef`** when you need imperative control over the array contents without triggering re-renders.
- Avoid `JSON.stringify` for dependency arrays due to performance costs and reliability issues.

## Frequently Asked Questions

### Why does useEffect run infinitely when I pass a new array literal?

When you write `useEffect(() => {...}, [newArray])` where `newArray` is defined inline like `[item1, item2]`, JavaScript creates a new array object on every render. React's `areHookInputsEqual` function detects this reference change and assumes the dependencies changed, triggering the effect. The effect then likely updates state, causing another render and creating another new array, resulting in an infinite loop.

### Is useMemo the best way to stabilize an array for useEffect?

Yes, `useMemo` is the idiomatic React solution for this problem. It aligns with React's declarative paradigm and hook rules. By placing the individual values that determine your array's contents in `useMemo`'s dependency list, you ensure the array reference only changes when those underlying values actually change. This satisfies React's shallow equality check in `areHookInputsEqual` without manual reference management.

### Can I use useRef instead of useMemo for dynamic arrays?

You can use `useRef` as an alternative, particularly when you need imperative control or want to avoid the overhead of memoization calculations. However, `useRef` requires manual synchronization—you must explicitly check if the contents changed before updating `ref.current`. Unlike `useMemo`, `useRef` does not automatically respond to prop or state changes, making it more error-prone for declarative data flows. Use `useRef` only when `useMemo` is insufficient for your specific use case.