How to Optimize React Props for Better Performance

Optimizing React props is essential for preventing unnecessary component re-renders and ensuring your application runs smoothly. This article covers the most effective strategies for prop optimization, including using React.memo, stabilizing prop references with useCallback and useMemo, avoiding inline object definitions, and restructuring component props to minimize render triggers.

Use React.memo to Prevent Unnecessary Re-renders

By default, a React component re-renders whenever its parent component renders, even if its props have not changed. Wrapping a functional component in React.memo performs a shallow comparison of the incoming props. If the props are identical to the previous render, React skips rendering the component.

import React from 'react';

const MyComponent = React.memo(({ name }) => {
  console.log('Rendered!');
  return <div>Hello, {name}</div>;
});

React.memo is highly effective for pure functional components that render often with the same props.

Avoid Inline Objects and Arrays

Passing object or array literals directly as props creates a new reference in memory on every single render. Because React uses shallow comparison (strict equality ===) to compare props, these new references force child components to re-render, rendering React.memo useless.

Avoid this:

// This creates a new object reference on every render
<UserCard info={{ name: 'John', age: 30 }} />

Do this instead: Declare the object outside the component if it is static, or use useMemo if it depends on state.

// Static object outside the component
const USER_INFO = { name: 'John', age: 30 };

function App() {
  return <UserCard info={USER_INFO} />;
}

Stabilize Functions with useCallback

Similar to inline objects, defining functions inline inside a component creates a new function instance on every render. To keep function references stable across renders, wrap your event handlers and callback functions in the useCallback hook.

import React, { useState, useCallback } from 'react';

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

  // The reference to this function remains the same
  const handleClick = useCallback(() => {
    console.log('Button clicked');
  }, []);

  return <ChildComponent onClick={handleClick} />;
}

By using useCallback, the ChildComponent will not re-render unless its other props change.

Use useMemo for Expensive Computations and Derived Props

If you need to calculate a prop value using complex logic, use useMemo to cache the result. This prevents the recalculation on every render and ensures that the prop reference only changes when the dependencies change.

import React, { useMemo } from 'react';

function ProductList({ items }) {
  // Only recalculates when 'items' changes
  const expensiveFilteredItems = useMemo(() => {
    return items.filter(item => item.price > 100);
  }, [items]);

  return <ListDisplay items={expensiveFilteredItems} />;
}

Pass Primitives Instead of Large Objects

Whenever possible, pass primitive values (strings, numbers, booleans) instead of whole objects as props. Primitive values are compared by value, not by reference. This makes it easier for React to determine if a prop has actually changed.

Instead of passing an entire user object:

<ProfileCard user={userObject} />

Pass only the specific primitives needed:

<ProfileCard username={userObject.username} email={userObject.email} />

This ensures that the ProfileCard component only re-renders if the username or email values change, ignoring updates to other unrelated fields in the userObject.