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>
))}
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>
))}
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));
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]
);
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);
}, []);
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);
};
}, []);
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>;
}
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>;
}
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:
-
Memoize Functions with
useCallback
const handleClick = useCallback(() => {
console.log("Clicked!");
}, []);
-
Memoize Expensive Computations with
useMemo
const sortedList = useMemo(() => {
return bigList.sort();
}, [bigList]);
-
Prevent Unnecessary Re-renders with
React.memo
const Child = React.memo(({ value }) => <div>{value}</div>);
- 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)