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.Element
because the body is JSX. - No implicit
children
prop — 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
-
children
auto‑included viaPropsWithChildren<P>
. -
Return type is
ReactElement | null
— avoids olderundefined
pitfalls. -
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-typos
and 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)