If you've been working with React and TypeScript for a while, you've probably stumbled into the age-old debate: should you use React.FC or just write standard function components?
As someone who's been building React applications for over 8 years, I've seen this discussion evolve from a heated controversy to a more nuanced choice. The good news? The most problematic issues with React.FC were fixed in React 18 and TypeScript 5.1. The even better news? There's now a clear winner for most use cases.
Let me walk you through both approaches and show you why the development community has largely settled on standard function components.
The Two Approaches: A Quick Comparison
Before diving deep, let's see both patterns in action:
Standard Function Component (The Winner)
interface ButtonProps {
title: string;
disabled?: boolean;
onClick: () => void;
}
// Clean, simple, and TypeScript-friendly
function Button({ title, disabled = false, onClick }: ButtonProps) {
return (
<button disabled={disabled} onClick={onClick}>
{title}
</button>
);
}
React.FC Approach
interface ButtonProps {
title: string;
disabled?: boolean;
onClick: () => void;
}
// More verbose, with explicit typing
const Button: React.FC<ButtonProps> = ({
title,
disabled = false,
onClick,
}) => {
return (
<button disabled={disabled} onClick={onClick}>
{title}
</button>
);
};
Both work, but there's more to the story.
Why React.FC Fell Out of Favor
Back in the early days of TypeScript + React, React.FC seemed like the obvious choice. It was the "official" way to type components, right? Well, not quite.
The Children Problem (Now Fixed!)
The biggest issue was that React.FC automatically added a children prop to every component, whether you wanted it or not:
// Before React 18 - this was BROKEN
const Button: React.FC<{ title: string }> = ({ title }) => {
return <button>{title}</button>;
};
// This would compile without errors (but shouldn't!)
<Button title="Click me">
<span>This shouldn't be allowed!</span>
</Button>;
This implicit children prop broke type safety and led to confusing bugs. Thankfully, React 18 fixed this – React.FC no longer includes children automatically.
Generic Components: Still a Problem
Here's where things get interesting. If you're building reusable components with generics, React.FC still struggles:
// This works beautifully with standard functions
interface ListProps<T> {
items: T[];
renderItem: (item: T) => React.ReactNode;
}
function GenericList<T extends { id: string }>({
items,
renderItem,
}: ListProps<T>) {
return (
<ul>
{items.map((item) => (
<li key={item.id}>{renderItem(item)}</li>
))}
</ul>
);
}
// Usage - TypeScript knows exactly what T is!
<GenericList
items={[
{ id: "1", name: "John" },
{ id: "2", name: "Jane" },
]}
renderItem={(user) => <span>{user.name}</span>} // user is properly typed!
/>;
Try doing that with React.FC – you'll run into type inference issues that'll make you want to throw your laptop out the window.
Modern Best Practices with Standard Functions
Let me show you the patterns I use in production applications:
Basic Component with Default Props
interface CardProps {
title: string;
description?: string;
variant?: "primary" | "secondary";
}
function Card({ title, description, variant = "primary" }: CardProps) {
return (
<div className={`card card--${variant}`}>
<h3>{title}</h3>
{description && <p>{description}</p>}
</div>
);
}
Component That Accepts Children
When you need children, be explicit about it:
import { PropsWithChildren } from "react";
interface ContainerProps {
maxWidth?: "sm" | "md" | "lg";
className?: string;
}
function Container({
maxWidth = "md",
className,
children,
}: PropsWithChildren<ContainerProps>) {
return (
<div className={`container container--${maxWidth} ${className || ""}`}>
{children}
</div>
);
}
Advanced: Generic Component with Constraints
This is where standard functions really shine:
interface SelectOption {
value: string;
label: string;
}
interface SelectProps<T extends SelectOption> {
options: T[];
value?: T["value"];
onChange: (option: T) => void;
placeholder?: string;
}
function Select<T extends SelectOption>({
options,
value,
onChange,
placeholder = "Choose an option...",
}: SelectProps<T>) {
return (
<select
value={value}
onChange={(e) => {
const option = options.find((opt) => opt.value === e.target.value);
if (option) onChange(option);
}}
>
<option value="">{placeholder}</option>
{options.map((option) => (
<option key={option.value} value={option.value}>
{option.label}
</option>
))}
</select>
);
}
The Verdict: Standard Functions Win
While React.FC is no longer broken, standard function components remain the better choice for several reasons:
- Simpler syntax - Less boilerplate, more readable
- Better generic support - Essential for reusable components
- Explicit contracts - You decide exactly what props to accept
- Modern JavaScript patterns - Works beautifully with ES6 destructuring
- Community consensus - Most style guides and teams prefer this approach
Quick Reference: The Complete Comparison
| Feature | Standard Function | React.FC | Winner |
|---|---|---|---|
| Syntax Simplicity | ✅ Clean & minimal | ❌ More verbose | Standard |
| Generic Support | ✅ Full TypeScript support | ❌ Type inference issues | Standard |
| Children Handling | ✅ Explicit with PropsWithChildren |
✅ Fixed in React 18 | Tie |
| Default Props | ✅ ES6 destructuring | ✅ Works fine | Tie |
| Return Type | ✅ Inferred automatically | ✅ Explicitly typed | Tie |
| Bundle Size | ✅ No extra overhead | ✅ Negligible difference | Tie |
| Learning Curve | ✅ Standard TypeScript | ❌ Framework-specific | Standard |
| Industry Adoption | ✅ Widely preferred | ❌ Falling out of favor | Standard |
My Recommendation
Stick with standard function components. They're simpler, more flexible, and align with modern React development patterns. The only time I might consider React.FC is when working with a legacy codebase that already uses it consistently.
Here's my go-to template for new components:
interface ComponentProps {
// Define your props here
}
function Component({ prop1, prop2 }: ComponentProps) {
// Your component logic
return (
// Your JSX
);
}
export default Component;
Clean, simple, and it just works.
What's Your Take?
The React TypeScript ecosystem keeps evolving, and while this debate has largely settled, I'm curious about your experience. Are you team React.FC or team standard functions? Have you run into any edge cases that influenced your choice?
Feel free to reach out on X or check out more of my React insights on GitHub. I'm always up for a good technical discussion!
What patterns are you using in your React projects? Let's keep the conversation going!