Chapter 5: Components: Everything is a Nail When You Have a Hammer
In React’s world, everything is a component. Your header? Component. Your button? Component. That space between two words? Probably should be a component. That feeling of existential dread as you contemplate your career choices? Definitely a component.
React took the reasonable idea of “reusable UI pieces” and turned it into a religion where every div must be wrapped in a function that returns a div.
The Component All The Things Philosophy
Here’s how normal people build a webpage:
<header>
<h1>My Website</h1>
<nav>
<a href="/">Home</a>
<a href="/about">About</a>
</nav>
</header>
Here’s how React developers build the same thing:
const NavLink = ({ href, children }) => (
<a href={href}>{children}</a>
);
const Navigation = () => (
<nav>
<NavLink href="/">Home</NavLink>
<NavLink href="/about">About</NavLink>
</nav>
);
const SiteTitle = ({ title }) => (
<h1>{title}</h1>
);
const Header = () => (
<header>
<SiteTitle title="My Website" />
<Navigation />
</header>
);
const App = () => (
<div>
<Header />
</div>
);
We’ve turned 7 lines of HTML into 25 lines of React components. Progress!
“But it’s reusable!” React developers cry. Sure, if you plan on having multiple headers on your page. Which you don’t. Because that’s not how headers work.
Functional vs Class: The Civil War
React has had two ways to write components, and the community has spent years fighting about which is better.
Class components (The Old Testament):
class Counter extends React.Component {
constructor(props) {
super(props);
this.state = { count: 0 };
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
this.setState({ count: this.state.count + 1 });
}
render() {
return (
<button onClick={this.handleClick}>
Count: {this.state.count}
</button>
);
}
}
Functional components (The New Testament):
const Counter = () => {
const [count, setCount] = useState(0);
return (
<button onClick={() => setCount(count + 1)}>
Count: {count}
</button>
);
};
The functional version is shorter, which React developers interpret as “better.” Never mind that it requires understanding hooks, closures, and why your event handler creates a new function every render.
The real tragedy? React spent years telling us class components were the way. Then they introduced hooks and said, “Actually, forget everything we told you. Functions are the way now.”
Thousands of codebases are now split between old class components and new functional components, like geological layers showing the different eras of React’s evolution.
When a Div Would Have Been Fine
My favorite React anti-pattern is the component that’s just a div with extra steps:
const Container = ({ children }) => (
<div className="container">{children}</div>
);
const Wrapper = ({ children }) => (
<div className="wrapper">{children}</div>
);
const Box = ({ children }) => (
<div className="box">{children}</div>
);
// Usage
<Container>
<Wrapper>
<Box>
Content
</Box>
</Wrapper>
</Container>
Congratulations, you’ve created three components that do exactly what a div with a class would do, but with more code, more complexity, and more opportunities for bugs.
Props: Passing Problems Down the Tree
Props are how React components communicate. It’s like a game of telephone, but every player has to explicitly pass the message, and if anyone forgets, the whole thing breaks.
const Button = ({ onClick, disabled, loading, color, size, variant, children, ...rest }) => {
return (
<button
onClick={onClick}
disabled={disabled || loading}
className={`btn btn-${size} btn-${variant} btn-${color}`}
{...rest}
>
{loading ? 'Loading...' : children}
</button>
);
};
Look at that prop list. That button component now has more configuration options than a German car.
And here’s the best part: when you need to add a new prop, you have to:
- Add it to the component
- Add it to every place that uses the component
- Add it to the TypeScript interface (if you’re using TypeScript)
- Add it to the prop-types (if you’re stuck in 2018)
- Add it to the Storybook story (if you’re fancy)
- Add it to the tests (if you’re responsible)
All to pass one value to a button.
The Everything Is a Component Mindset
React has trained developers to think everything needs to be a component. I’ve seen actual production code like this:
const Space = () => <span> </span>;
const Comma = () => <span>,</span>;
const Period = () => <span>.</span>;
// Usage
<div>
Hello<Comma /><Space />World<Period />
</div>
This is not a joke. Someone, somewhere, thought punctuation needed to be componentized.
Component Composition: The Lego Blocks That Don’t Fit
React promotes “composition over inheritance,” which sounds smart until you realize composition in React means nesting components like Russian dolls:
<ThemeProvider theme={darkTheme}>
<AuthProvider>
<RouterProvider>
<StoreProvider>
<IntlProvider>
<ErrorBoundary>
<Suspense fallback={<Loading />}>
<App />
</Suspense>
</ErrorBoundary>
</IntlProvider>
</StoreProvider>
</RouterProvider>
</AuthProvider>
</ThemeProvider>
This is called “Provider Hell” and it’s what happens when every feature needs its own wrapper component. Your actual app is buried seven layers deep, like a digital archaeological site.
The Render Method: Where Logic Goes to Die
Every React component has one job: return JSX. This leads to the render method (or return statement) becoming a dumping ground for logic:
const UserProfile = ({ user }) => {
return (
<div>
{user ? (
user.isActive ? (
user.isPremium ? (
<PremiumProfile user={user} />
) : user.isTrial ? (
<TrialProfile user={user} />
) : (
<BasicProfile user={user} />
)
) : (
<InactiveProfile user={user} />
)
) : (
<LoginPrompt />
)}
</div>
);
};
This is what we call “ternary hell,” and it’s what happens when your template language is actually a programming language pretending to be a template language.
Component State: Everyone’s Business Is No One’s Business
Every component can have its own state, which sounds great until you realize that means state is scattered across your entire component tree like hidden land mines:
const Form = () => {
const [name, setName] = useState('');
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const [confirmPassword, setConfirmPassword] = useState('');
const [errors, setErrors] = useState({});
const [isSubmitting, setIsSubmitting] = useState(false);
const [isSuccess, setIsSuccess] = useState(false);
// 200 lines of event handlers and validation logic
return (
// JSX that's longer than your last relationship
);
};
Now you have seven pieces of state for one form. Change any of them, and the entire component re-renders. Efficiency!
The Reusability Myth
The biggest lie React tells is that components are reusable. Sure, in theory. In practice:
// The "reusable" component
const Button = ({
variant,
size,
color,
disabled,
loading,
fullWidth,
icon,
iconPosition,
onClick,
type,
form,
className,
children,
...rest
}) => {
// 50 lines of logic to handle all the variants
};
// How it's actually used
<Button
variant="primary"
size="large"
onClick={handleSubmit}
>
Submit
</Button>
// Six months later
<Button
variant="primary-outline-gradient"
size="large-but-not-too-large"
onClick={handleSubmit}
specialCaseForThisOneFeature={true}
legacyMode={true}
newDesignSystem={false}
>
Submit
</Button>
Your “reusable” component becomes a Swiss Army knife that does everything poorly instead of one thing well.
The Component File Structure Debate
React doesn’t tell you how to organize your components, so developers have invented seventeen different “best practices”:
// Option 1: By Type
/components
/buttons
/forms
/layouts
// Option 2: By Feature
/features
/auth
/dashboard
/settings
// Option 3: Atomic Design
/atoms
/molecules
/organisms
/templates
/pages
// Option 4: The "I Give Up"
/components
Button.jsx
Form.jsx
... 200 other files
Teams spend more time discussing file structure than building features. And whatever structure you choose, someone will tell you it’s wrong.
Performance: Death by a Thousand Components
Every component is a function call. Every render is a function execution. When everything is a component, everything is overhead:
// This simple list becomes:
// - 1 List component render
// - 100 ListItem component renders
// - 100 ListItemText component renders
// - 100 ListItemIcon component renders
// = 301 function calls to display a list
const List = ({ items }) => (
<ul>
{items.map(item => (
<ListItem key={item.id} item={item} />
))}
</ul>
);
const ListItem = ({ item }) => (
<li>
<ListItemIcon icon={item.icon} />
<ListItemText text={item.text} />
</li>
);
const ListItemIcon = ({ icon }) => (
<span className="icon">{icon}</span>
);
const ListItemText = ({ text }) => (
<span className="text">{text}</span>
);
Meanwhile, in vanilla JavaScript:
const html = items.map(item =>
`<li><span class="icon">${item.icon}</span><span class="text">${item.text}</span></li>`
).join('');
list.innerHTML = html;
One operation. Done. But that’s not the React way.
In Defense of Components (Grudgingly)
Okay, components aren’t entirely evil:
- Encapsulation: Components do encapsulate functionality
- Testing: Isolated components are easier to test
- Team Collaboration: Different people can work on different components
- Mental Model: Thinking in components can help organize complex UIs
But we’ve taken it too far. Not everything needs to be a component. Sometimes a function is just a function. Sometimes HTML is just HTML.
The Reality Check
Here’s what we’ve done: we’ve taken the simple concept of “reusable UI pieces” and turned it into a complex hierarchy of nested functions that return other functions that eventually return something that looks like HTML but isn’t.
We’ve created problems just so we can solve them with components. We’ve added complexity just so we can manage it with more components.
The Path Forward
You’re going to create components. Lots of them. Too many of them. You’ll componentize things that don’t need to be componentized. You’ll create abstractions that abstract nothing. You’ll build reusable components that are used exactly once.
And when someone asks why everything needs to be a component, you’ll say, “That’s the React way.”
Because it is. React is components all the way down. Like turtles, but more frustrating and with more props.
In the next chapter, we’ll dive into state management - React’s solution to the problem of components needing to share data, which wouldn’t be a problem if everything wasn’t a component in the first place.
But first, take a moment to appreciate the simplicity we’ve lost. Remember when you could just write HTML and it would just work? Those were the days.
Now, if you’ll excuse me, I need to refactor my refactored refactoring into a more reusable component. It’s currently only used in seventeen places, which clearly isn’t reusable enough.