Alright, let’s settle a holy war, shall we? When you define a functional component in TypeScript, you’re immediately faced with a choice: do you slap a React.FC type on it, or do you just type the props and let the return type be inferred? This seems like a trivial stylistic decision, but it comes with real-world consequences that range from “mildly annoying” to “genuinely problematic.” Let’s break it down.

The Case for React.FC

The React.FC (or its longer alias, React.FunctionComponent) approach is the old guard. It was the officially recommended way for a long time, and you’ll see it in a ton of legacy code. Here’s what it looks like:

import React from 'react';

interface WelcomeProps {
  name: string;
  enthusiasmLevel?: number;
}

const Welcome: React.FC<WelcomeProps> = ({ name, enthusiasmLevel = 1 }) => {
  const exclamation = '!'.repeat(enthusiasmLevel);
  return <h1>Hello, {name}{exclamation}</h1>;
};

The React.FC type does a few things for you automagically:

  1. It explicitly states that this is a function that returns a React element (or null).
  2. It provides type checking for the component’s props.
  3. It types the children prop for you, even if you didn’t ask for it. This is its most controversial feature.

That last point is a double-edged sword. It’s convenient if your component should accept children. But if it’s a simple button or image component that shouldn’t have any, it’s now a liar. Users of your component can write <Welcome name="Steve"><div>Why can I put this here?</div></Welcome> and TypeScript will just shrug. The component might not render those children, leading to confusion. This is the designers’ “questionable choice” I mentioned—baking in an implicit children prop was a bold, and many would argue misguided, move.

The Modern, Leaner Approach

The increasingly popular and modern alternative is to simply type the function’s arguments and let TypeScript infer the return type.

interface WelcomeProps {
  name: string;
  enthusiasmLevel?: number;
}

const Welcome = ({ name, enthusiasmLevel = 1 }: WelcomeProps) => {
  const exclamation = '!'.repeat(enthusiasmLevel);
  return <h1>Hello, {name}{exclamation}</h1>;
};

Look at that. Cleaner, right? No React.FC noise. The function returns a JSX element, and TypeScript is brilliantly capable of figuring out that its return type is JSX.Element. The biggest win? No implicit children prop. If you want children, you have to explicitly add it to your interface like a responsible adult. This is a feature, not a bug—it makes your component’s API more honest.

interface CardProps {
  title: string;
  children: React.ReactNode; // Explicit is better than implicit.
}

const Card = ({ title, children }: CardProps) => {
  return (
    <div className="card">
      <h2>{title}</h2>
      <div className="content">{children}</div>
    </div>
  );
};

The Devil’s in the Details: Generics and defaultProps

Now, React.FC wasn’t all bad. It had two semi-useful tricks up its sleeve. First, it made typing generic components slightly more ergonomic.

// With React.FC - the generic parameter is on the type
const GenericList: React.FC<{ items: T[]; renderItem: (item: T) => React.ReactNode }> = (props) => { ... }

// Without React.FC - the generic parameter is on the function itself
const GenericList = <T,>({ items, renderItem }: { items: T[]; renderItem: (item: T) => React.ReactNode }) => { ... }

The second trick was its (now deprecated) interaction with the defaultProps API. React.FC would automatically combine your props interface with the defaultProps static property to provide correct typing. However, since we’ve moved on to defining defaults directly in the function signature (like enthusiasmLevel = 1 above), this “feature” is now a relic of a bygone era and not a valid reason to use React.FC.

The Verdict and Best Practices

So, which one should you use? For 99% of new code, skip React.FC. The direct return type approach is simpler, more explicit, and prevents the sneaky children issue. The TypeScript team themselves have even hinted that React.FC offers little value. The only remaining use case might be if you personally prefer the look of it for generic components or you’re working in a codebase where consistency with existing React.FC usage is more important than the switch.

Here’s your practical takeaway:

  1. Default to typing props directly on the function argument.
  2. Set default values in the function signature (prop = defaultValue). It’s cleaner and doesn’t rely on deprecated patterns.
  3. If you need children, add it explicitly to your props interface. Use the broad React.ReactNode type for maximum flexibility.
  4. If you see React.FC in a codebase, don’t panic. It’s not wrong, it’s just a bit outdated. You can leave it be or refactor it as you touch those files, following the team’s style guide.

You’re not just choosing a type; you’re choosing a philosophy of explicit over implicit. And in my trench-filled experience, explicit always wins in the long run.