Fixing Infinite Re-renders When You Pass Object Literals as React Props

July 18, 2026 8 min read

Your component re-renders, passes an object literal as a prop, the child re-renders, triggers a useEffect, updates state, and the whole thing starts over. Within milliseconds your browser tab is frozen and the console is screaming Too many re-renders. The culprit is almost always a reference equality problem with an object you created inline.

This is one of those bugs that looks completely innocent in the code but causes catastrophic runtime behavior. Understanding the exact mechanism makes it trivially easy to fix β€” and to avoid in the future.

What you'll learn

  • Why JavaScript reference equality makes inline objects dangerous as props
  • How React's re-render cycle turns a single object literal into an infinite loop
  • Five concrete fixes ranked by situation: moving objects outside components, useMemo, useRef, flattening props, and React.memo
  • The common mistakes that keep the loop alive even after you think you've fixed it

The Root Cause: Reference Equality in JavaScript

JavaScript compares objects by reference, not by value. Two objects that look identical are not equal unless they point to the same place in memory.

const a = { color: 'red' };
const b = { color: 'red' };

console.log(a === b); // false β€” different references
console.log(a === a); // true  β€” same reference

This is not a React quirk. It is how JavaScript works at its core. React simply exposes the consequences more dramatically than most code you write day-to-day.

How React Decides to Re-render

When a parent component re-renders, React calls the parent function again from top to bottom. It then compares each prop it is about to pass to a child against the prop it passed on the previous render. The comparison is a strict equality check (===) on the prop value itself.

For primitives like strings and numbers, this is fine. 'red' === 'red' is always true. But for objects, React is comparing references, and a brand-new object literal created inside the render function gets a brand-new reference every single time.

If the child component has a useEffect that lists that object in its dependency array, React sees a new object on every render and fires the effect every render. If that effect sets state, the parent re-renders again β€” and so it goes, forever. You can read more about how React's development mode adds another layer to this problem in this breakdown of why useEffect fires twice in dev but once in production.

Why Object Literals Are the Silent Culprit

The dangerous pattern looks completely natural, which is why it catches so many developers off guard.

function ParentComponent() {
  const [count, setCount] = React.useState(0);

  return (
    <ChildComponent
      config={{ theme: 'dark', size: 'large' }} {/* new object every render */}
      onClick={() => setCount(c => c + 1)}
    />
  );
}

function ChildComponent({ config, onClick }) {
  React.useEffect(() => {
    console.log('config changed:', config);
    // do something with config...
  }, [config]); // fires every render because config is always a new reference

  return <button onClick={onClick}>Click</button>;
}

Every time ParentComponent renders β€” for any reason β€” it creates a new { theme: 'dark', size: 'large' } object. Even though the values are identical, the reference is new. React dutifully fires the useEffect, and if that effect triggers a state update anywhere up the tree, you have your loop.

The same problem applies to inline arrays ([]), inline functions (() => {}), and inline class instances. Anything that gets constructed fresh inside a render function body.

Spotting the Infinite Loop in Practice

React will throw Error: Too many re-renders. React limits the number of renders to prevent an infinite loop when it detects the problem synchronously. But often the loop is more subtle β€” the component keeps re-rendering at high frequency without crashing, burning CPU cycles and making the UI sluggish.

To diagnose it, open React DevTools and use the Profiler tab. Enable Record why each component rendered before starting a profiling session. Trigger the interaction that causes the repeated renders, then inspect the flame graph.

If the child component repeatedly shows:

Props changed:

config

filters

options

even though the values appear identical, you've likely found a reference equality problem.

The profiler helps distinguish between components that legitimately re-render because their data changed and those that re-render because new object references are being created on every pass.

Fix 1: Move Static Objects Outside the Component

If the object never changes, don't recreate it inside the component.

Instead of:

function Parent() {
    return (
        <Child
            config={{
                theme: "dark",
                size: "large"
            }}
        />
    );
}

Move it outside:

const DEFAULT_CONFIG = {
    theme: "dark",
    size: "large"
};

function Parent() {
    return (
        <Child config={DEFAULT_CONFIG} />
    );
}

Now every render uses the exact same object reference.

This is the simplest and most efficient solution for constants.

Fix 2: Memoize Dynamic Objects with useMemo

Sometimes the object depends on props or state.

Instead of recreating it:

const config = {
    theme,
    size
};

use:

const config = React.useMemo(() => ({
    theme,
    size
}), [theme, size]);

Now React only creates a new object when either dependency actually changes.

This is the most common solution for configuration objects passed to child components.

Fix 3: Store Stable References with useRef

Occasionally an object should never change after initialization.

Example:

const config = React.useRef({
    timeout: 5000,
    retries: 3
});

Use:

config.current

throughout the component.

Unlike useMemo, useRef doesn't recreate values when dependencies change.

It's ideal for mutable objects that shouldn't trigger re-renders.

Fix 4: Flatten Props Instead of Passing Objects

Sometimes you don't need an object at all.

Instead of:

<Child
    settings={{
        darkMode,
        compact
    }}
/>

pass:

<Child
    darkMode={darkMode}
    compact={compact}
/>

Primitive values compare much more predictably than object references.

This also makes components easier to understand and test.

Fix 5: Combine Stable Props with React.memo

Even stable objects won't prevent unnecessary rendering if the child itself always re-renders.

Wrap the child:

const Child = React.memo(function Child(props) {
    ...
});

React now performs a shallow comparison of props before rendering.

Combined with stable object references, this can dramatically reduce unnecessary work.

Arrays Have the Same Problem

Inline arrays behave exactly like objects.

Avoid:

<Chart
    colors={["red", "blue", "green"]}
/>

Instead:

const colors = React.useMemo(() => [
    "red",
    "blue",
    "green"
], []);

Every render previously created a new array reference.

Memoization fixes it.

Inline Functions Can Trigger Similar Issues

Functions are also objects in JavaScript.

Avoid:

<Child
    onSave={() => saveItem(id)}
/>

when the child depends on callback identity.

Instead:

const handleSave = React.useCallback(() => {
    saveItem(id);
}, [id]);

useCallback stabilizes the function reference between renders.

Watch Your useEffect Dependencies

This effect:

useEffect(() => {
    fetchData(config);
}, [config]);

only behaves correctly if config itself is stable.

Otherwise:

Render

↓

New object

↓

Effect runs

↓

State update

↓

Render

↓

New object

↓

Effect runs

The dependency array isn't broken.

The dependency reference is.

Memoization Isn't a Magic Fix

Developers sometimes write:

const config = useMemo(() => ({
    theme,
    options
}), [theme]);

Notice the mistake.

options isn't listed.

Now the memoized object becomes stale.

Always include every value used inside the memo callback.

Ignoring dependency warnings often creates harder bugs than the one you're trying to solve.

Deep Objects Need Extra Care

Suppose:

const config = useMemo(() => ({
    theme,
    filters
}), [theme, filters]);

If filters itself changes reference every render, the memo becomes ineffective.

Sometimes you must stabilize nested objects first.

Work from the deepest changing reference outward.

Avoid Memoizing Everything

useMemo has a cost.

Avoid wrapping trivial values:

const title = useMemo(() => "Dashboard", []);

There's no benefit.

Reserve memoization for:

  • Objects
  • Arrays
  • Expensive computations
  • Derived data
  • Callback functions passed to children

State Updates Inside Effects

Another subtle loop:

useEffect(() => {
    setConfig({
        theme: "dark"
    });
}, []);

Looks harmless.

But if another effect depends on config and recreates another object, the cycle can continue.

Review every state update involving objects.

Custom Hooks Can Hide the Problem

Example:

const config = useSettings();

If useSettings returns:

return {
    theme,
    language
};

without memoization,

every consumer receives a new object every render.

Custom hooks should also return stable references whenever practical.

React Strict Mode Makes It More Noticeable

In development, React Strict Mode intentionally invokes certain lifecycle behavior twice to help uncover side effects.

This often makes unstable references appear much worse during development.

If your application only "breaks" in development, don't disable Strict Mode immediately.

Instead, investigate whether unstable objects, arrays, or callbacks are causing repeated effects.

Fixing the underlying reference issue usually resolves the development behavior as well.

Common Mistakes

Memoizing the Wrong Object

Stabilize the object being passedβ€”not an unrelated value.


Forgetting Dependencies

Incomplete dependency arrays create stale values.


Passing Nested Inline Objects

Even if the outer object is memoized, nested objects can still change every render.


Assuming React.memo Solves Everything

React.memo only works when incoming props remain referentially stable.


Creating Objects Inside JSX

Inline object literals always create fresh references.

Choosing the Right Fix

SituationRecommended Solution
Constant configurationMove object outside the component
Object depends on state or propsuseMemo
Mutable object that should never trigger rendersuseRef
Passing many simple valuesFlatten props
Prevent unnecessary child rendersReact.memo with stable props
Callback passed to childrenuseCallback

Selecting the appropriate technique keeps components predictable while avoiding unnecessary optimization.

Debugging Checklist

When you encounter unexpected re-renders:

  • Enable React DevTools Profiler and record why components render.
  • Look for object, array, or callback props changing every render.
  • Search for inline object literals inside JSX.
  • Verify useMemo and useCallback dependency arrays.
  • Check custom hooks for newly created objects.
  • Confirm child components wrapped with React.memo receive stable references.
  • Review every useEffect dependency that contains objects or arrays.

Following this checklist usually identifies the source of infinite or excessive re-renders within minutes.

Final Thoughts

Infinite re-render loops caused by inline object literals aren't a React bugβ€”they're the natural consequence of JavaScript's reference equality rules combined with React's rendering model. Every time a component renders, new object and array literals receive new memory references, and React correctly treats them as changed props. When those changing references appear in dependency arrays or memoized components, seemingly harmless code can quickly spiral into excessive rendering or even an infinite loop.

The solution isn't to avoid objects altogether, but to keep their references stable. Moving constants outside components, using useMemo for derived objects, storing long-lived values in useRef, flattening props where appropriate, and combining these techniques with React.memo creates components that are both efficient and predictable. Understanding when React compares values versus references will help you eliminate an entire class of rendering bugs before they ever reach production.

 

Frequently Asked Questions

Why does passing an object as a prop cause a React component to re-render infinitely?

Every time a parent component renders, an object literal written inside the JSX gets a new memory reference, even if the values inside it haven't changed. React compares props with strict equality, so it always sees a 'new' prop and re-renders the child. If the child's useEffect depends on that prop, the effect fires again and can trigger more state updates, creating an infinite loop.

Does wrapping a child in React.memo stop infinite re-renders caused by object props?

Not on its own. React.memo uses shallow equality, which compares object props by reference. If the parent creates a new object literal on every render, React.memo sees a new reference and re-renders the child anyway. You need to also stabilize the object in the parent using useMemo so the reference only changes when the data actually changes.

How do I know if a useMemo dependency array is correct for an object?

List every variable from the component scope that is read inside the useMemo callback. If any of those variables change and your object should reflect that change, they must be in the dependency array. An empty array means the object never updates, which is usually wrong β€” move a truly static object to module scope instead.

Can inline arrays cause the same infinite re-render problem as inline objects?

Yes. Arrays are objects in JavaScript and are compared by reference, not by value. An inline array like columns={['name', 'email']} creates a new reference on every render and causes the exact same problem. Apply the same fixes: define it at module scope if it's static, or wrap it in useMemo if it depends on component state or props.

What is the fastest way to find which prop is causing an infinite re-render?

Open the React DevTools Profiler, record a session, and look for components that re-render continuously with no user interaction. Click into those renders to see which props changed between renders. The prop showing a different value on every frame β€” especially an object or function β€” is the culprit.

πŸ“€ Share this article

Sign in to save

Comments (0)

No comments yet. Be the first!

Leave a Comment

Sign in to comment with your profile.

πŸ“¬ Weekly Newsletter

Stay ahead of the curve

Get the best programming tutorials, data analytics tips, and tool reviews delivered to your inbox every week.

No spam. Unsubscribe anytime.