Typing Functional Components and Props in TypeScript0%
Typing React Hooks, Generics, and Refs

Typing Functional Components and Props in TypeScript

Beginner12 min read•Updated: Jul 11, 2026
Study MaterialsCheatsheet

Typing Functional Components and Props in TypeScript

TypeScript is the foundation of enterprise React development. Typing functional components and their props guarantees compile-time type safety, eliminates undefined is not a function runtime errors, and provides rich autocompletion across design systems.


1. Modern Component Typing: Direct Interface vs React.FC

In early TypeScript React, React.FC (or React.FunctionComponent) was common. However, modern enterprise codebases prefer direct prop typing:

Why React.FC has Fallen Out of Favor

  • In React 17 and earlier, React.FC implicitly injected children: ReactNode into every component, even if the component was a leaf node (like a self-closing badge or button).
  • It complicates generics: <T>(props: Props<T>) is awkward to write with React.FC.
  • Direct function typing is cleaner and mirrors standard TypeScript functions.
TSX
// ✅ Modern Best Practice: Direct Props Typing
interface UserCardProps {
userId: string;
name: string;
email: string;
role?: "admin" | "editor" | "viewer"; // Optional union prop
isActive: boolean;
onSelect: (id: string) => void; // Typed callback function
}
 
export function UserCard({
userId,
name,
email,
role = "viewer", // Default prop value
isActive,
onSelect,
}: UserCardProps) {
return (
<div
onClick={() => onSelect(userId)}
className={`p-4 rounded-lg border cursor-pointer ${
isActive ? "border-cyan-500 bg-cyan-950/20" : "border-slate-800 bg-slate-900"
}`}
>
<h3 className="font-bold text-white">{name}</h3>
<p className="text-xs text-slate-400">{email}</p>
<span className="inline-block mt-2 px-2 py-0.5 text-xs font-mono uppercase bg-slate-800 text-cyan-400 rounded">
{role}
</span>
</div>
);
}

2. Typing children Explicitly

When a component accepts nested children, define children explicitly using React.ReactNode:

TSX
import React, { ReactNode } from "react";
 
interface CardContainerProps {
title: string;
children: ReactNode; // Accepts JSX elements, strings, numbers, fragments, or null
actionSlot?: ReactNode; // Optional slot for buttons or badges
}
 
export function CardContainer({ title, children, actionSlot }: CardContainerProps) {
return (
<section className="bg-slate-900 border border-slate-800 rounded-xl p-6">
<header className="flex justify-between items-center mb-4">
<h2 className="text-lg font-bold text-white">{title}</h2>
{actionSlot && <div>{actionSlot}</div>}
</header>
<main className="text-slate-300">{children}</main>
</section>
);
}

3. Extending Native HTML Attributes

When building design system primitives (like custom buttons or text fields), extend native HTML attributes so your components accept standard attributes (id, aria-label, disabled, style, className) automatically:

TSX
import React, { ButtonHTMLAttributes } from "react";
 
interface CustomButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
variant?: "primary" | "secondary" | "danger";
isLoading?: boolean;
}
 
export function CustomButton({
variant = "primary",
isLoading = false,
children,
className = "",
disabled,
...restProps // Forward all standard button attributes (type, onClick, aria-*, etc.)
}: CustomButtonProps) {
const variantStyles = {
primary: "bg-cyan-600 hover:bg-cyan-500 text-white",
secondary: "bg-slate-800 hover:bg-slate-700 text-slate-200",
danger: "bg-rose-600 hover:bg-rose-500 text-white",
};
 
return (
<button
disabled={disabled || isLoading}
className={`px-4 py-2 rounded-lg font-medium transition flex items-center justify-center gap-2 disabled:opacity-50 ${variantStyles[variant]} ${className}`}
{...restProps}
>
{isLoading ? (
<span className="h-4 w-4 animate-spin rounded-full border-2 border-white border-t-transparent" />
) : null}
{children}
</button>
);
}

Interactive Quiz

Practice Quiz

Question 1
Why is direct props typing (function Button(props: ButtonProps)) preferred over React.FC in modern React?
Question 2
What TypeScript type should you use to type children when accepting any valid renderable React content?
Question 3
How do you allow a custom button component to accept all standard native HTML <button> attributes like disabled, type, and onKeyDown?
Question 4
How should an optional callback prop onSelect that receives a string id be typed?
Question 5
What is the benefit of defining union literal types (e.g. role?: "admin" | "editor" | "viewer") over plain string?
Next Lesson

Typing React Hooks, Generics, and Refs

Continue learning with hands-on practice, examples, and exercises in the upcoming topic.

Practice Quiz

Test your understanding of this lesson with 5 questions. Each question has one correct answer.

PrevNext