6 Common React Anti-Patterns That Are Hurting Your Code Quality
6 Common React Anti-Patterns That Are Hurting Your Code Quality: When I first started working with React, everything seemed straightforward — just a few components, some props, and a touch of state. But as the projects grew, I began running into issues that weren’t immediately obvious. Over time, I realized that these issues were patterns in disguise — not good ones, but anti-patterns.
Anti-patterns are like sneaky pitfalls: they seem harmless at first but cause trouble as your codebase grows. By ‘patterns’ here, I mainly mean the code I see quite often in many codebases, written by developers of different experience levels — not necessarily ‘patterns’ in the sense of formal design patterns. I’m sharing some of the most common anti-patterns I’ve encountered in React development, with the hope that recognizing them will help you keep your code clean, scalable, and easy to maintain.
I have also discussed the solution for these Anti-Patterns in my book React Anti-Patterns and only some common ones are discussed in this article.
6 Common React Anti-Patterns That Are Hurting Your Code Quality
1. Props Drilling
The Problem: Props drilling happens when you pass props from a top-level component down through several layers of intermediary components until they finally reach the component that actually needs them. It can be especially problematic when your component tree is deep and the prop is used by only one component at the end of that chain.

6 Common React Anti-Patterns That Are Hurting Your Code Quality
Why It’s Bad: This results in tightly coupled components that are hard to refactor. If a prop requirement changes, you may need to update every component that lies between the top and the bottom. The whole system becomes fragile and difficult to maintain.
Imagine a SearchableList
component that passes an onItemClick
function down through List
, and then to ListItem
. Each intermediary component must handle the props even if they don’t actually need them. This adds a lot of complexity for very little gain.
Code Example — Bad Approach:
function SearchableList({ items, onItemClick }) {
return (
<div className="searchable-list">
<List items={items} onItemClick={onItemClick} />
</div>
);
}
function List({ items, onItemClick }) {
return (
<ul className="list">
{items.map((item) => (
<ListItem key={item.id} data={item} onItemClick={onItemClick} />
))}
</ul>
);
}
function ListItem({ data, onItemClick }) {
return (
<li className="list-item" onClick={() => onItemClick(data.id)}>
{data.name}
</li>
);
}
You have to pass onItemClick
all the way down from the SearchableList
to ListItem
, all the component in between has to know the prop, the type of the function, but don’t do anything with it.
This is pretty common when people just try to “fix” the problem on the surface, or don’t have time to think how big the impact could be.
6 Common React Anti-Patterns That Are Hurting Your Code Quality
2. In-Component Data Transformation
The Problem: Transforming data directly inside a component’s useEffect
or in the render function itself often feels like the simplest approach. You fetch the data, transform it as needed, and then set the state—all in one place.

6 Common React Anti-Patterns That Are Hurting Your Code Quality
Why It’s Bad: This mixes concerns within your component, bloating it with multiple responsibilities — fetching, transforming, and rendering. It also makes testing difficult and limits the reusability of the transformation logic. As the transformations get more complex, this makes the component much harder to understand and maintain.
Consider a UserProfile
component that fetches user data and transforms it by combining the first and last name or formatting the address. Placing all of this logic inside useEffect
means that every change requires a deep dive into both fetching and transformation—not very efficient.
Code Example — In-Component Transformation:
function UserProfile({ userId }) {
const [user, setUser] = useState(null);
useEffect(() => {
fetch(`/api/users/${userId}`)
.then(response => response.json())
.then(data => {
// Transforming data right inside the component
const transformedUser = {
name: `${data.firstName} ${data.lastName}`,
age: data.age,
address: `${data.addressLine1}, ${data.city}, ${data.country}`
};
setUser(transformedUser);
});
}, [userId]);
return (
<div>
{user && (
<>
<p>Name: {user.name}</p>
<p>Age: {user.age}</p>
<p>Address: {user.address}</p>
</>
)}
</div>
);
}
This example shows how data fetching and transformation are combined directly in the useEffect
, leading to a tightly coupled component and making reuse or testing difficult.
3. Complicated Logic in Views
The Problem: Have you ever been tempted to include some business logic directly in your component because “it’s just a small piece”? That’s how it starts, but soon the component is filled with conditional statements, mapping logic, and computations.

6 Common React Anti-Patterns That Are Hurting Your Code Quality
Why It’s Bad: Components are meant to focus on presenting UI, not implementing business rules. The more logic you put into your components, the harder it is to reuse them or to change the rules without affecting the rendering. It leads to bulky components that are hard to test and understand.
Imagine a component that not only displays order details but also calculates discounts, shipping costs, and estimated taxes — the type of logic that could be much more reusable if it lived in separate service functions or hooks.
4. Lack of Tests
The Problem: Skipping tests might feel like a timesaver, especially when you’re working against a deadline. But as React components often handle complex functionality — like managing form state or coordinating API calls — this can lead to hard-to-diagnose bugs.
Why It’s Bad: Without proper unit or integration tests, there’s no safety net to catch errors when refactoring or adding features. Every change becomes a risky endeavor, and you find yourself doing a lot of manual testing, which still can’t cover all the scenarios.
I remember shipping a feature where a shopping cart failed to update under certain edge cases. Proper unit tests would have caught the issues before it reached production.
6 Common React Anti-Patterns That Are Hurting Your Code Quality
5. Duplicated Code
The Problem: Copy-pasting a piece of code is often the easiest solution — you’ve already written it, so why not reuse it directly? The problem is that every duplicate is a maintenance burden.
Why It’s Bad: When the requirements change, you’ll need to update every duplicated instance, and missing just one of them can cause bugs and inconsistencies. This is not just about laziness; it’s about ensuring that your logic remains centralized and easy to modify.
Imagine a formatDate()
function that appears in multiple components because you pasted it each time you needed it. When the format requirement changes, it becomes a search-and-hope-you-found-them-all task.
Code Example — Duplicated Code:
function AdminList(props) {
const filteredUsers = props.users.filter(user => user.isAdmin);
return <List items={filteredUsers} />;
}
function ActiveList(props) {
const filteredUsers = props.users.filter(user => user.isActive);
return <List items={filteredUsers} />;
}
This example shows how repeated logic — filtering users — is present in different components, leading to code duplication and maintainability challenges.
Please note that you might need to balance what is duplication, I have recently published an article on the topic Why Duplicating Code in UI Could Lead to Better Maintenance. Please have a read to have a more balanced view and understanding.
6 Common React Anti-Patterns That Are Hurting Your Code Quality
6. Long Components with Too Many Responsibilities
The Problem: You might think of a component like OrderContainer
that manages everything related to an order—validation, error handling, fetching data, and rendering UI. It seems convenient until it grows into a “God component.”
Why It’s Bad: Components should follow the Single Responsibility Principle (SRP). When they have too many responsibilities, they become complex and hard to debug, understand, or extend. They’re also very difficult to test, especially if one piece of logic depends on another.
In one project, I had a form component that handled validation, submission, error display, and even managing the global state. Breaking it into smaller components and extracting hooks for different tasks made the code much easier to work with.
6 Common React Anti-Patterns That Are Hurting Your Code Quality
Conclusion
These are just a few of the anti-patterns I’ve learned to recognise and avoid over my years working with React. The key to maintainable, scalable React applications is knowing when you’re falling into these traps and refactoring accordingly.
I’ll be diving into detailed solutions for these anti-patterns in my upcoming newsletter issues, where I’ll show exactly how to refactor and improve these code examples step by step. If you’re interested, consider subscribing to my newsletter or grabbing a copy of my book, React Anti-Patterns.
Let me know — have you struggled with any of these anti-patterns in your own projects? Which one have you found the hardest to overcome?
If you like the reading, please Sign up for my mailing list. I share Clean Code and Refactoring techniques weekly via blogs.
6 Common React Anti-Patterns That Are Hurting Your Code Quality