React.js dominates the frontend landscape for good reason—its component-based architecture, flexibility, and powerful ecosystem make building modern web apps a joy. But even experienced developers fall into common pitfalls that tank performance, create bugs, or make code unmaintainable.
After reviewing hundreds of React codebases, we've identified the 10 most critical mistakes that separate amateur code from production-ready applications. Here's how to avoid them and level up your React skills.
Table of Contents
- 1. State Management Gone Wrong
- 2. useEffect Dependency Hell
- 3. Global State Overuse
- 4. Memory Leak Disasters
- 5. List Rendering Performance Killers
- 6. Silent Error Failures
- 7. Type Safety Neglect
- 8. Performance Optimization Blind Spots
- 9. Logic-UI Code Mixing
- 10. Accessibility Oversights
- Quick Reference Checklist
- FAQ
1. Not Managing the State Properly
The Problem: State mismanagement is the #1 cause of React bugs and performance issues.
Common Mistakes:
- Direct State Mutation
const [items, setItems] = useState([1, 2, 3]);
items.push(4); // Mutating state directly
setItems(items);
- Unnecessary State Lifting
const [modalOpen, setModalOpen] = useState(false); // Only used in one component
- Storing Derived Data
const [activeUsers, setActiveUsers] = useState([]); // Derived from users
How to Fix:
- Treat state as immutable:
setItems([...items, 4]);
setItems(items.filter(item => item.id !== targetId));
- Keep state local to components where possible.
- Compute derived values in render instead of storing them.
2. Ignoring Dependency Arrays in useEffect
The Problem: Incorrect dependencies can cause infinite loops, stale closures, and missed updates.
Fix:
- Include all dependencies.
useEffect(() => {
fetchData(userId);
}, [userId]);
- Stabilise objects/functions with useMemo or define inside useEffect.
- Use the ESLint plugin react-hooks/exhaustive-deps to catch mistakes.
3. Overusing Context or Redux for Everything
The Problem: Not everything needs a global state; overuse creates performance bottlenecks.
Fix:
- Use local state for UI components (modals, dropdowns).
- Use Context/Redux only for truly shared state: authentication, theme, cart, notifications.
- Split contexts to avoid unnecessary re-renders.
4. Forgetting to Clean Up Effects {#mistake-4}
The Problem: Effects not cleaned up lead to memory leaks.
Fix:
- Return cleanup functions in useEffect:
useEffect(() => {
const timer = setInterval(fetchLatestData, 5000);
return () => clearInterval(timer);
}, []);
- Close connections and remove listeners when components unmount.
5. Rendering Large Lists Without Optimisation
The Problem: Rendering thousands of items without keys or optimisation kills performance.
Fix:
- Always use unique, stable keys.
- Use virtualisation libraries like react-window.
- Consider pagination or infinite scroll for large datasets.
6. Ignoring Error Handling in Async Operations
The Problem: Unhandled errors crash your app.
Fix:
- Wrap async calls in try-catch blocks.
- Use state for loading and error.
- Implement error boundaries for components.
7. Not Using PropTypes or TypeScript for Type Safety
The Problem: Runtime type errors are common in React apps.
Fix:
- Prefer TypeScript for compile-time safety.
- Use PropTypes in JavaScript projects.
- Safely handle nullable or optional data.
8. Not Optimising Performance in Heavy Components
The Problem: Components re-render unnecessarily.
Fix:
- Memoize components with React.memo.
- Use useMemo for expensive calculations.
- Use useCallback for function references.
- Profile performance using React Profiler.
9. Mixing Business Logic and UI {#mistake-9}
The Problem: Tangled logic and UI make code hard to maintain.
Fix:
- Separate business logic into custom hooks.
- Use helper functions for calculations.
- Keep components focused on rendering.
10. Not Writing Accessible Components {#mistake-10}
The Problem: Inaccessible components exclude users and violate web standards.
Fix:
- Use semantic HTML and ARIA attributes.
- Ensure keyboard navigation.
- Test with accessibility tools like axe and Lighthouse.
Quick Reference Checklist
State Management:
- State is immutable
- State lifted only when necessary
- The derived state is computed
Effects & Side Effects:
- All dependencies listed
- Cleanup functions returned
- ESLint exhaustive-deps enabled
Performance:
- Keys used in large lists
- React.memo applied
- useMemo/useCallback for expensive logic
Error Handling:
- try-catch for async
- Error boundaries implemented
- User-friendly messages
Code Quality:
- TypeScript/PropTypes used
- Logic separated from UI
- Single responsibility per component
Accessibility:
- Proper labels & ARIA
- Keyboard accessible
- Screen reader friendly
Final Thoughts
React's power comes with responsibility. These 10 mistakes can make the difference between a maintainable, high-performance app and a buggy, slow mess.
Remember:
- Keep the state immutable
- Manage dependencies carefully
- Optimize performance
- Handle errors gracefully
- Use type safety
- Make apps accessible
Need expert React development for your next project?
Get Professional React Consultation →
FAQ
Q1: What is the most common mistake React developers make?
A: Mismanaging states, like direct mutation or unnecessary lifting, are the most common causes of bugs and performance issues.
Q2: How can I prevent unnecessary re-renders in React?
A: Use React.memo, useMemo, and useCallback. Split contexts and keep state local where possible.
Q3: Should I use Redux or Context for all state management?
A: Only for state shared across multiple components. Local state (useState) is enough for UI components.
Q4: How do I avoid memory leaks?
A: Always clean up effects in useEffect by returning cleanup functions. Remove listeners, clear timers, and close subscriptions.
Q5: How can I make my React app accessible?
A: Use semantic HTML, proper form labels, ARIA attributes, keyboard navigation, and test with tools like axe or Lighthouse.
Q6: Is TypeScript necessary for React?
A: Not mandatory, but highly recommended for type safety, reducing runtime errors, and improving maintainability.
Q7: How do I handle errors in async operations?
A: Wrap async calls in try-catch, manage loading and error state, and consider error boundaries.