Stop the Debate: When to Use FC<Props> vs Inferred Arrow Functions in React + TypeScript
Turning a two‑line snippet into a masterclass on generics, JSX.Element return types, and bundle hygiene.
TL;DR
| Style | Snippet | Pros | Cons |
|---|---|---|---|
| Inferred Arrow | export const GifList = ({ gifs }: Props) => … |
🧬 TypeScript infers props; slimmer bundle; flexible return type | Must annotate return type yourself if you care; no built‑in children
|
FC<Props> |
export const GifList: FC<Props> = ({ gifs }) => … |
Auto‑adds children, propTypes, defaultProps; readable to TS newcomers |
Adds an extra import + generic; can hide implicit children; historically caused undefined JSX return edge cases |
1 The Two Contenders
A. Vanilla Arrow with Inferred Props
interface Gif {
id: string;
url: string;
title: string;
width: number;
height: number;
}
interface Props {
gifs: Gif[];
}
export const GifList = ({ gifs }: Props) => (
<div className="gifs-container">
{gifs.map(({ id, url, title, width, height }) => (
<div key={id} className="gif-card">
<img src={url} alt={title} />
<h3>{title}</h3>
<p>
{width} × {height} (1.5 MB)
</p>
</div>
))}
</div>
);
- TypeScript infers the return type as
JSX.Elementbecause the body is JSX. - No implicit
childrenprop — ideal when the component is truly leaf‑only.
B. Explicit Functional Component (FC) Generic
import type { FC } from "react";
export const GifList: FC<Props> = ({ gifs }) => (
/* same JSX */
);
What FC<Props> really is:
type FC<P = {}> = FunctionComponent<P>;
interface FunctionComponent<P = {}> {
(props: PropsWithChildren<P>, context?: any): ReactElement | null;
propTypes?: WeakValidationMap<P>;
defaultProps?: Partial<P>;
displayName?: string;
}
Key differences
-
childrenauto‑included viaPropsWithChildren<P>. -
Return type is
ReactElement | null— avoids olderundefinedpitfalls. -
Static props (
displayName,propTypes) are typed out of the box.
2 Performance & Bundle Size
The extra import type { FC } is erased at compile‑time.
Actual cost: < 0.1 kB gzipped — negligible.
Still, arrow functions keep code minimal when you ship many micro‑components.
3 When to Pick Which
| Scenario | Recommended Style | Reason |
|---|---|---|
Pure display, no children
|
Inferred arrow | Enforces explicitness; prevents accidental children |
Layout wrappers (Card, Grid) |
FC<Props> |
Free children typing |
| Higher‑order components |
FC or custom generic |
Easier to compose |
| Design‑system libraries | FC |
Newcomer readability |
| Strict null control | FC |
Return type excludes undefined
|
4 Edge‑Case Gotchas
4.1 Hidden children
// Accepts children even if you didn't intend it
export const Toast: FC<ToastProps> = ({ message }) => <div>{message}</div>;
4.2 memo() + FC
Older TS versions might lose generics:
export const Memoized = memo(GifList) as FC<Props>;
4.3 forwardRef
Prefer explicit generics instead of FC:
export const Button = forwardRef<HTMLButtonElement, Props>(
(props, ref) => <button ref={ref} {...props} />
);
5 Migrating a Codebase
-
Audit components — switch to arrow style if they never read
children. -
ESLint — enable
react/no-typosand related rules. -
Guidelines — document: “Atoms use arrows; slots use
FC.” - Prettier — keep consistent export style.
6 Cheat‑Sheet
| Question | Quick Answer |
|---|---|
Does FC hurt runtime perf? |
No. Pure type import. |
Is FC deprecated? |
No. Optional. |
| Default props with arrows? | GifList.defaultProps = { gifs: [] } |
Does FC type context? |
Only context?: any. Prefer hooks. |
Final Thoughts
The choice signals intent more than anything:
- Arrow inference → “Leaf node, no children.”
-
FC<Props>→ “Drop stuff inside me.”
Use the one that documents intent best, lint ruthlessly, and iterate like the React‑TS scientist you are. 🧑🔬
✍️ Written by: Cristian Sifuentes – Full-stack dev crafting scalable apps with [NET - Azure], [Angular - React], Git, SQL & extensions. Clean code, dark themes, atomic commits
Happy coding — and may your prop types always align! 🚀

Top comments (0)