DEV Community

Jay Sarvaiya
Jay Sarvaiya

Posted on

5 Common Mistakes React Developers Make (and How to Fix Them)

React is one of the most popular front-end libraries in the world. It powers apps at companies like Meta, Netflix, Airbnb, and countless startups. It’s flexible, fast, and makes UI development fun.

But here’s the catch even experienced React developers fall into some common traps. These mistakes might not break your app immediately, but they can cause:

  • Performance bottlenecks
  • Unpredictable bugs
  • Bloated code
  • Hard-to-maintain projects

In this guide, we’ll go deep into 5 common React mistakes and see how to avoid them with best practices.


1. Using Index as a Key in Lists

When rendering lists in React, you must provide a key prop. A lot of devs reach for the array index:

{items.map((item, index) => (
  <li key={index}>{item.name}</li>
))}
Enter fullscreen mode Exit fullscreen mode

This looks fine… until you start adding, removing, or reordering items.

Problem:

React uses the key to decide if it should re-use an element or recreate it. If you use the index:

  • Adding/removing items breaks state (checkboxes, inputs reset).
  • Animations look glitchy.
  • Debugging becomes painful.

Fix: Use a Unique Identifier

{items.map(item => (
  <li key={item.id}>{item.name}</li>
))}
Enter fullscreen mode Exit fullscreen mode

If your data doesn’t have an ID, generate one with uuid or a similar package.

Pro Tip: Only use the index as a key if the list is static and will never change (like rendering stars in a rating component).


2. Overusing State

It’s tempting to put everything into useState:

const [filteredData, setFilteredData] = useState(data.filter(d => d.active));
Enter fullscreen mode Exit fullscreen mode

Problem:

  • Storing derived data (something you can compute from existing state) leads to unnecessary re-renders.
  • Your app becomes harder to reason about because state gets out of sync.

Fix: Use useMemo for Derived Data

const filteredData = useMemo(
  () => data.filter(d => d.active),
  [data]
);
Enter fullscreen mode Exit fullscreen mode

Now React only recalculates when data changes.

Pro Tip: Keep your state as minimal as possible. Store the source of truth, not everything derived from it.


3. Forgetting to Cleanup Side Effects

When you use useEffect, React runs your code after every render. Many devs forget cleanup:

useEffect(() => {
  window.addEventListener("resize", handleResize);
}, []);
Enter fullscreen mode Exit fullscreen mode

Problem:

  • Event listeners stack up.
  • Memory leaks occur.
  • Old references cause weird bugs.

Fix: Always Return a Cleanup Function

useEffect(() => {
  window.addEventListener("resize", handleResize);

  return () => {
    window.removeEventListener("resize", handleResize);
  };
}, []);
Enter fullscreen mode Exit fullscreen mode

Pro Tip: This applies to any side effect (timeouts, intervals, subscriptions, sockets). Always ask:

  • Do I need to remove/unsubscribe?
  • Will this cause memory leaks if the component unmounts?

4. Mixing Logic and UI

A common beginner mistake is fetching data directly in a component:

function UserProfile() {
  const [user, setUser] = useState(null);

  useEffect(() => {
    fetch("/api/user/1")
      .then(res => res.json())
      .then(setUser);
  }, []);

  return <div>{user?.name}</div>;
}
Enter fullscreen mode Exit fullscreen mode

Problem:

  • Business logic is tangled with UI.
  • Hard to test.
  • Reuse is impossible.

Fix: Extract Logic into Custom Hooks

function useUser(id) {
  const [user, setUser] = useState(null);

  useEffect(() => {
    fetch(`/api/user/${id}`)
      .then(res => res.json())
      .then(setUser);
  }, [id]);

  return user;
}

function UserProfile() {
  const user = useUser(1);
  return <div>{user?.name}</div>;
}
Enter fullscreen mode Exit fullscreen mode

Pro Tip: If you notice useEffect + state logic repeating across components, that’s a signal to create a custom hook.


5. Ignoring Performance Tools

React is fast, but careless code can slow it down:

  • Components re-render too often.
  • Expensive calculations run on every render.
  • Large lists freeze the UI.

Common Issues:

  • Passing new inline functions as props (onClick={() => ...})
  • Rendering 1000+ items without virtualization
  • Not memoizing heavy computations

Fixes:

  1. Memoize Functions with useCallback
const handleClick = useCallback(() => {
  console.log("Clicked!");
}, []);
Enter fullscreen mode Exit fullscreen mode
  1. Memoize Expensive Computations with useMemo
const sortedList = useMemo(() => {
  return bigList.sort();
}, [bigList]);
Enter fullscreen mode Exit fullscreen mode
  1. Prevent Unnecessary Re-renders with React.memo
const Child = React.memo(({ value }) => <div>{value}</div>);
Enter fullscreen mode Exit fullscreen mode
  1. Profile Before Optimizing Use React DevTools Profiler to see which components re-render unnecessarily.

Pro Tip: Don’t optimize blindly. Always measure first! Premature optimization = wasted time.


Bonus Mistake: Ignoring Accessibility

Animations and slick UI don’t matter if your app isn’t usable for everyone.

  • Use semantic HTML (<button> instead of <div onClick>).
  • Add aria-* attributes for screen readers.
  • Ensure keyboard navigation works.

Accessibility is not an afterthought—it’s part of good React development.


Final Thoughts

Mistakes are part of the learning process. But if you avoid these pitfalls:

  • Use proper keys
  • Keep state minimal
  • Clean up side effects
  • Separate logic with hooks
  • Optimize smartly

…your React apps will be faster, cleaner, and easier to maintain.

My challenge to you: Go back to one of your old React projects and check if you made any of these mistakes. Fixing them will instantly improve your codebase.

What’s the biggest React mistake you made, and what did you learn from it? Drop it in the comments—I’d love to hear your stories!

Top comments (0)