The truth is, components are deceptively simple. It’s easy to get started—define a function, return some JSX, and call it a day. But writing components that are clean, maintainable, and pleasant to work with? That’s a whole different ballgame.
Without realizing it, we create components that are:
In this post, I’ll walk you through the most common mistakes developers make with React components. More importantly, I’ll show you how to fix them without tearing apart your entire app.
Whether you’re just getting started or have years of experience, these tips will help you write components that are not just functional, but a joy to maintain.
Let’s talk about the classic blunder we’ve all made: the everything component. You’ve seen it—it starts small and innocent, maybe as a simple form or dashboard. Fast forward a bit, and now it’s managing state, handling API calls, formatting data, and possibly brewing your morning coffee.
// Please, no more of this const UserDashboard = () => { const [userData, setUserData] = useState(null); const [orders, setOrders] = useState([]); const [notifications, setNotifications] = useState([]); const [settings, setSettings] = useState({}); const [isEditing, setIsEditing] = useState(false); const [activeTab, setActiveTab] = useState('profile'); // 15 separate useEffects // 10 different event handlers // Multiple conditional renders // 300+ lines of chaos };
Sound familiar?
Your component might have turned into an "everything component" if:
The solution? Instead of a single everything component, split responsibilities into smaller, focused pieces.
// A cleaner, smarter approach const UserDashboard = () => { return ( <div> <UserProfile /> <OrderHistory /> <NotificationCenter /> <UserSettings /> </div> ); };
When refactoring, don’t break components based on how they look on the screen. Split them by responsibility. Ask yourself: Does this functionality deserve its own component? If it’s handling something distinct—like user profiles or order history—it probably does.
Tip: A good component does one thing and does it well. If you struggle to describe its purpose in a single sentence, chances are it’s trying to do too much.
Let’s discuss the not-so-fun game of “pass the props”. If you’ve ever passed the same prop through multiple components just to reach a deeply nested child, you’ve been stuck in prop drilling hell.
// Please, no more of this const UserDashboard = () => { const [userData, setUserData] = useState(null); const [orders, setOrders] = useState([]); const [notifications, setNotifications] = useState([]); const [settings, setSettings] = useState({}); const [isEditing, setIsEditing] = useState(false); const [activeTab, setActiveTab] = useState('profile'); // 15 separate useEffects // 10 different event handlers // Multiple conditional renders // 300+ lines of chaos };
This approach isn’t just annoying—it creates long-term problems. Imagine needing to rename the user prop. Suddenly, you’re updating it in five or more places. Worse, you end up tying components to data they don’t even use.
There’s no need to play hot potato with your props. Here are two practical solutions to avoid drilling altogether.
1. Use Context for Shared Data
If a piece of data is accessed across different parts of your app, React’s Context API can simplify things.
// A cleaner, smarter approach const UserDashboard = () => { return ( <div> <UserProfile /> <OrderHistory /> <NotificationCenter /> <UserSettings /> </div> ); };
2. Use Composition for Flexibility
Instead of forcing props through layers, restructure components so they only pass down what’s needed.
// This is exhausting const App = () => { const [user, setUser] = useState({}); return ( <Layout user={user}> <Sidebar user={user}> <Navigation user={user}> <UserMenu user={user} /> </Navigation> </Sidebar> </Layout> ); };
Key Takeaways
Context works well for app-wide data like user information, themes, or global settings. However, it’s not always the best option—don’t overuse it. For localized state, think about whether your component structure can be adjusted to avoid drilling entirely.
The goal is to make your components clear and maintainable. Avoiding prop drilling will save you time, frustration, and countless headaches down the road.
You've probably heard that famous quote about premature optimization being the root of all evil. Well, welcome to component-level evil. I'm talking about those times we try to make everything reusable before we even know if we'll need it twice.
Here's what it usually looks like:
const UserContext = createContext(); const App = () => { const [user, setUser] = useState({}); return ( <UserContext.Provider value={user}> <Layout> <Sidebar> <Navigation /> </Sidebar> </Layout> </UserContext.Provider> ); }; // Use it only where needed const UserMenu = () => { const user = useContext(UserContext); return <div>{user.name}</div>; };
Let your components evolve naturally. Build for what you know you need today. If new requirements arise, add features when you can clearly justify them. Premature optimization wastes time, adds complexity, and rarely pays off.
Remember: The YAGNI principle (You Aren’t Gonna Need It) applies to components too. The best abstractions come when you’ve actually encountered the problems they’re solving. Over-engineering might feel proactive, but simplicity wins every time.
Here’s a classic example of bad effect management. Look familiar?
// Focused components for better clarity const Navigation = ({ children }) => { return <nav>{children}</nav>; }; // Pass data only where required const App = () => { const user = useUser(); return ( <Layout> <Navigation> <UserMenu user={user} /> </Navigation> </Layout> ); };
Common Mistakes and Fixes
1) Messy Data Fetching
Bad data handling creates more bugs than it solves. Here's a cleaner approach:
// Behold, the over-engineered button const Button = ({ children, variant = 'primary', size = 'medium', isFullWidth = false, isDisabled = false, isLoading = false, leftIcon, rightIcon, onClick, customClassName, style, loadingText = 'Loading...', tooltipText, animationType, // ... 10 more props }) => { // 50 lines of prop processing logic return ( <button className={generateComplexClassNames()} > <h3> Why This Hurts </h3> <ul> <li>Your “simple” button now requires an instruction manual.</li> <li>Most of those 15+ props will never be used.</li> <li>Making updates becomes risky because you have to account for endless combinations.</li> <li>Writing tests becomes painful, with a hundred possible scenarios to consider.</li> </ul> <h3> Better Approach: </h3> <p>Instead of building for every imaginable scenario, start small and let your components grow as needed.<br> </p> <pre class="brush:php;toolbar:false">// Start simple const Button = ({ children, onClick, variant = 'primary' }) => { return ( <button className={`btn btn-${variant}`} onClick={onClick} > {children} </button> ); } // Create specific buttons when you actually need them const LoadingButton = ({ isLoading, children, ...props }) => { return ( <Button {...props}> {isLoading ? 'Loading...' : children} </Button> ); }
2) Forgetting Cleanup
Always clean up after yourself:
const UserProfile = ({ userId }) => { const [user, setUser] = useState(null); const [posts, setPosts] = useState([]); // Dependency array woes useEffect(() => { fetchUserData(userId); fetchUserPosts(userId); // No cleanup? Yikes. }, []); // eslint-disable-line react-hooks/exhaustive-deps // Multiple effects, all tangled useEffect(() => { const subscription = subscribeToUserStatus(userId); }, [userId]); // Cleanup? What cleanup? useEffect(() => { const interval = setInterval(checkNotifications, 5000); }, []); };
3) Ignoring Race Conditions
Avoid overlapping requests with this technique:
// Improved version const UserProfile = ({ userId }) => { const { data: user, isLoading } = useQuery( ['user', userId], () => fetchUserData(userId) ); // Keep concerns separate const { data: posts } = useQuery( ['posts', userId], () => fetchUserPosts(userId), { enabled: !!user } ); };
Quick Tips
Let’s talk about those sneaky performance issues. They’re the kind that fly under the radar because everything seems fine—until it isn’t. Let’s uncover these silent culprits and see how to fix them.
The Problem
Here’s a component with some subtle performance pitfalls:
// Please, no more of this const UserDashboard = () => { const [userData, setUserData] = useState(null); const [orders, setOrders] = useState([]); const [notifications, setNotifications] = useState([]); const [settings, setSettings] = useState({}); const [isEditing, setIsEditing] = useState(false); const [activeTab, setActiveTab] = useState('profile'); // 15 separate useEffects // 10 different event handlers // Multiple conditional renders // 300+ lines of chaos };
Can you spot the issues? Let’s break them down.
Fixes
1) Memoize Expensive Calculations
Instead of recalculating everything on each render, use useMemo to cache results:
// A cleaner, smarter approach const UserDashboard = () => { return ( <div> <UserProfile /> <OrderHistory /> <NotificationCenter /> <UserSettings /> </div> ); };
This avoids recalculating data and recreating event handlers on every render. It also prevents unnecessary re-renders of child components with memo.
2) Efficient State Updates
Poor state management can also kill performance. Here’s a better way to handle updates like search results:
// This is exhausting const App = () => { const [user, setUser] = useState({}); return ( <Layout user={user}> <Sidebar user={user}> <Navigation user={user}> <UserMenu user={user} /> </Navigation> </Sidebar> </Layout> ); };
Debouncing ensures we’re not fetching data on every keystroke, reducing unnecessary API calls and re-renders.
Quick Performance Tips
Building components isn’t rocket science, but let’s be real—it’s easy to stumble into bad habits. I’ve made every one of these mistakes (and sometimes still do). That’s okay. What matters is catching them early, fixing them, and avoiding rough codebase.
Quick Checklist for Better Components
✅ Single Responsibility: If you can’t summarize your component’s job in one sentence, it’s time to break it apart.
✅ Props Management: Passing props through layers upon layers? Consider using Context or leveraging composition instead.
✅ State & Effects: Keep effects focused, clean them up properly, and let modern tools handle complex data fetching.
✅ Performance: Don’t optimize for the sake of it—measure first. Use tools like memo, useMemo, and useCallback smartly when needed.
✅ Start Simple: Solve the problems you have now, not the ones you might have someday.
The best components aren’t flashy or overly clever—they’re the ones your team can read and maintain without groaning six months down the line.
Remember: these aren’t hard rules, just guidelines. You’ll break them sometimes, and that’s perfectly fine. The goal isn’t perfection—it’s building components that don’t make you question your career choices when you revisit them later.
The above is the detailed content of Stop Making These Component Mistakes. For more information, please follow other related articles on the PHP Chinese website!