Back to Blog
Common React Antipatterns in Enterprise-Scale Apps
Photo from Unsplash

The Compound Cost of Antipatterns

In a small project, minor inefficiencies in React code are rarely noticed. But in an enterprise application with hundreds of components and complex data flows, "code smells" compound. A prop-drilled state here, a misplaced useEffect there — and suddenly you're chasing render bugs in production at midnight.

As Senior Developers, our job is to recognize these antipatterns early and fix them before they scale.

1. Prop Drilling vs. Context

We've all seen it: a piece of state passed down through five layers of components that don't even use it.

// ❌ Antipattern — UserAvatar doesn't need theme but has to pass it down
function Page({ theme }: { theme: Theme }) {
  return <Sidebar theme={theme} />;
}
function Sidebar({ theme }: { theme: Theme }) {
  return <Nav theme={theme} />;
}
function Nav({ theme }: { theme: Theme }) {
  return <UserAvatar theme={theme} />;
}

// ✅ Solution — consume context directly where it's needed
const ThemeContext = createContext<Theme>(defaultTheme);

function UserAvatar() {
  const theme = useContext(ThemeContext);
  return <img style={{ border: `2px solid ${theme.primary}` }} />;
}

Use the React Context API for global theme/auth data, or a library like Zustand for complex domain state.

2. Over-using useEffect for Data Fetching

Manual useEffect fetching leads to race conditions, stale data, and loading-state boilerplate in every component.

// ❌ Antipattern — race conditions, no cache, resets on every render
useEffect(() => {
  fetch('/api/user').then(r => r.json()).then(setUser);
}, [userId]);

// ✅ Solution — React Server Component (Next.js App Router)
// app/profile/page.tsx
async function ProfilePage({ params }: { params: { id: string } }) {
  const user = await fetchUser(params.id); // server-side, cached, no effect needed
  return <UserProfile user={user} />;
}

For client-side data that genuinely needs reactivity, use TanStack Query — it handles caching, refetching, and error states automatically.

3. Array Index as key Prop

Using an array index as a key during list rendering is one of the most common causes of hard-to-debug UI glitches when lists reorder or filter.

// ❌ Antipattern — React can't track items correctly when list order changes
{items.map((item, index) => (
  <Card key={index} item={item} />
))}

// ✅ Solution — use a unique, persistent ID from your data
{items.map((item) => (
  <Card key={item.id} item={item} />
))}

The moment your list can be reordered, filtered, or updated, key={index} will cause subtle animation glitches and incorrect state retention.

Conclusion

Pick one of these three antipatterns and audit your current codebase for it this week. Prop drilling is usually the easiest to find — search for any prop that appears in more than two component signatures without being used in the first. Fix that, and you'll often untangle two other bugs for free.


Sources & References

  • React Documentation: "Thinking in React"
  • React Documentation: useContext
  • TanStack Query Documentation
  • "Effective TypeScript" by Dan Vanderkam (O'Reilly Media)
Newer Post

Designing for the System: Implementing Advanced Theme Engines

Older Post

Magento 2 to Headless: A Senior Developer’s Migration Guide

Suggested Reading

Architectural Note:This platform serves as a live research laboratory exploring the future of Agentic Web Engineering. While the technical architecture, topic curation, and professional history are directed and verified by Maas Mirzaa, the technical research, drafting, and code execution are augmented by AI Agents (Gemini). This synthesis demonstrates a high-velocity workflow where human architectural vision is multiplied by AI-powered execution.