DEV Community

Paul Adeyele
Paul Adeyele

Posted on

9 React Props Mistakes That Are Hurting Your App's Performance

Props in React might feel straightforward, but mishandling them can slow your app to a crawl. Over time, after coding and seeing some React projects, I’ve noticed ten props-related mistakes that keep popping up. These issues could be lurking in your code, too.

Don’t worry, though—we’re here to fix them together. Let’s dive in and make your React app faster and more efficient!

1} Stop Passing Entire Objects When Only Specific Properties Are Needed

Passing entire objects as props when your component only needs a couple of properties leads to unnecessary re-renders anytime that object updates—even if the values you're using remain the same.

// Not ideal
function UserProfile({ user }) {
  // Only uses name and email
  return (
    <div>
      <h2>{user.name}</h2>
      <p>{user.email}</p>
    </div>
  );
}

// Better
function UserProfile({ name, email }) {
  return (
    <div>
      <h2>{name}</h2>
      <p>{email}</p>
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

Only pass what’s necessary. By keeping props focused and minimal, you’ll reduce how often components re-render, giving your app a noticeable boost in performance.

2} Avoid Creating New Objects in Props on Every Render

Another hidden culprit is creating inline objects in props. When you do this, you create new object references every time your component renders. These new references force child components to re-render, even if the values are identical.

// Not efficient
function ParentComponent() {
  return (
    <ChildComponent 
      styles={{ margin: '20px', padding: '10px' }}
      config={{ theme: 'dark', animate: true }}
    />
  );
}

// Smarter approach
const styles = { margin: '20px', padding: '10px' };
const config = { theme: 'dark', animate: true };

function ParentComponent() {
  return (
    <ChildComponent 
      styles={styles}
      config={config}
    />
  );
}
Enter fullscreen mode Exit fullscreen mode

Take object definitions outside your component or use useMemo for dynamically created ones. It’s a simple tweak, but it can dramatically reduce unnecessary rendering and keep everything running smoothly.

3} Avoid Unnecessarily Spreading Props

Using prop spreading (...props) might feel convenient, but it often does more harm than good. It passes down props you don’t need, makes your component harder to debug, and can even trigger unwanted re-renders.

// Inefficient
function Parent() {
  const props = {
    name: 'John',
    age: 30,
    email: 'john@email.com',
    phone: '1234567890',
    address: '123 Street'
  };

  return <UserCard {...props} />;
}

// A better way
function Parent() {
  const props = {
    name: 'John',
    age: 30,
    email: 'john@email.com',
    phone: '1234567890',
    address: '123 Street'
  };

  return <UserCard name={props.name} email={props.email} />;
}
Enter fullscreen mode Exit fullscreen mode

By specifying only the props you need, you make your component cleaner and more predictable. This keeps your app faster and easier to maintain.

4} Always Memoize Callback Props

Unmemoized callback functions can silently hurt performance. Every time your component re-renders, a new function instance is created. This can break optimizations in child components using React.memo or cause unnecessary re-renders.

// Not optimal
function TodoList() {
  const handleClick = (id) => {
    // handle click
  };

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

// Optimal approach
function TodoList() {
  const handleClick = useCallback((id) => {
    // handle click
  }, []); // Include dependencies if needed

  return <TodoItem onClick={handleClick} />;
}
Enter fullscreen mode Exit fullscreen mode

Wrap callback props with useCallback when passing them to memoized components or using them in useEffect. This ensures stable references and avoids unnecessary updates.

5} Stop Prop Drilling Through Multiple Levels

Passing props through several components that don’t even use them is a surefire way to create unnecessary re-renders and messy code. This is called prop drilling, and it can make your app harder to manage as it grows.

// Not ideal
function GrandParent({ user }) {
  return <Parent user={user} />;
}

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

function Child({ user }) {
  return <span>{user.name}</span>;
}

// Smarter solution
function App() {
  const [user] = useState(userData);

  return (
    <UserContext.Provider value={user}>
      <GrandParent />
    </UserContext.Provider>
  );
}

function Child() {
  const user = useContext(UserContext);
  return <span>{user.name}</span>;
}
Enter fullscreen mode Exit fullscreen mode

Instead of passing props down through every layer, use tools like React Context or libraries like Zustand for managing deeply nested data. This approach keeps your code cleaner and avoids unnecessary renders.

6} Don’t Use Array Indexes as Keys

Using array indexes as keys might seem harmless, but it can cause subtle bugs and performance problems, especially in lists where items are reordered or removed.

// Problematic
function TodoList({ todos }) {
  return (
    <ul>
      {todos.map((todo, index) => (
        <TodoItem key={index} todo={todo} />
      ))}
    </ul>
  );
}

// Better approach
function TodoList({ todos }) {
  return (
    <ul>
      {todos.map((todo) => (
        <TodoItem key={todo.id} todo={todo} />
      ))}
    </ul>
  );
}
Enter fullscreen mode Exit fullscreen mode

Always use stable, unique identifiers as keys. This helps React track your components properly, ensuring smooth updates and maintaining state accurately.

7} Stop Passing Down Unused Props

Passing unnecessary props can bloat your components and trigger avoidable re-renders. Every extra prop adds to the overhead, even if it’s not being used in the component.

// Inefficient
function UserCard({ user, theme, analytics, permissions, ...otherProps }) {
  // Only uses name and avatar
  return (
    <div>
      <img src={user.avatar} alt={user.name} />
      <h2>{user.name}</h2>
    </div>
  );
}

// Streamlined
function UserCard({ avatar, name }) {
  return (
    <div>
      <img src={avatar} alt={name} />
      <h2>{name}</h2>
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

Refactor your components regularly and remove any props that aren’t essential. A leaner component means fewer re-renders and a faster app.

8} Always Use Proper Prop Types

Skipping PropTypes or TypeScript is a common mistake that can lead to bugs and runtime errors. These tools help catch prop-related issues during development, making your app more robust and easier to debug.

// Risky approach
function Button(props) {
  return <button onClick={props.onClick}>{props.children}</button>;
}

// Safer with PropTypes
Button.propTypes = {
  onClick: PropTypes.func.isRequired,
  children: PropTypes.node.isRequired,
};

// Even better with TypeScript
interface ButtonProps {
  onClick: () => void;
  children: React.ReactNode;
}

function Button({ onClick, children }: ButtonProps) {
  return <button onClick={onClick}>{children}</button>;
}
Enter fullscreen mode Exit fullscreen mode

Using TypeScript or PropTypes not only helps you spot problems early but also makes your components more predictable and maintainable.

9} Never Mutate Props Directly

Directly changing props goes against React's immutability principles, often leading to unexpected bugs and missed updates.

// Problematic approach
function TodoList({ todos }) {
  const handleComplete = (index) => {
    todos[index].completed = true; // Mutating the prop directly
  };

  return (
    <ul>
      {todos.map((todo, index) => (
        <TodoItem 
          key={todo.id} 
          todo={todo} 
          onComplete={() => handleComplete(index)} 
        />
      ))}
    </ul>
  );
}

// Correct method
function TodoList({ todos, updateTodo }) {
  const handleComplete = (id) => {
    updateTodo(id, { completed: true }); // Updating through props
  };

  return (
    <ul>
      {todos.map((todo) => (
        <TodoItem 
          key={todo.id} 
          todo={todo} 
          onComplete={() => handleComplete(todo.id)} 
        />
      ))}
    </ul>
  );
}
Enter fullscreen mode Exit fullscreen mode

Keep props immutable by using functions or state updates instead. This ensures that React can track changes properly and re-render components when needed.

Conclusion

These prop mistakes might seem small, but they stack up to create serious performance issues over time. To keep your React app running smoothly:

  • Only pass the props that are actually needed by components.
  • Use useCallback to memoize functions and avoid unnecessary renders.
  • Rely on state management tools like Context or Zustand instead of drilling props.
  • Never mutate props directly—always work with immutable updates.
  • Use TypeScript or PropTypes to enforce type safety and catch bugs earlier.

Tools to help you optimize:

  • React DevTools Performance Tab: Pinpoint performance bottlenecks.
  • why-did-you-render: Detect unnecessary renders automatically.
  • ESLint React Hooks Plugin: Ensure proper hook usage.
  • TypeScript: Add static typing for better reliability.

Fix these issues today, and you’ll notice your app feels faster, more responsive, and easier to maintain.

Happy coding!!

Top comments (0)