Typing Props and Children

Props Type

Describe props with a type or interface and annotate the destructured parameter. Avoid React.FC in new code; a plain function with typed props is simpler and is what the React and Next.js docs use. (React itself is covered in the React material.)

type ProductCardProps = {
  name: string;
  price: number;
  imageUrl?: string;
  badge?: "new" | "sale" | "bestseller";
  onAddToCart: (qty: number) => void;
};

export function ProductCard({ name, price, imageUrl = "/placeholder.png", badge, onAddToCart }: ProductCardProps) {
  return (
    <div className="card">
      {badge && <span className={`badge badge-${badge}`}>{badge}</span>}
      <img src={imageUrl} alt={name} />
      <h3>{name}</h3>
      <p>Rs. {price.toLocaleString("en-IN")}</p>
      <button onClick={() => onAddToCart(1)}>Add to cart</button>
    </div>
  );
}

// <ProductCard name="Kettle" price={1299} badge="hot" />  -> error: "hot" not allowed, onAddToCart missing

children and Other React Types

import type { ReactNode, ComponentProps, CSSProperties } from "react";

type CardProps = { title: string; children: ReactNode; style?: CSSProperties };

export function Card({ title, children, style }: CardProps) {
  return <section style={style}><h2>{title}</h2>{children}</section>;
}

// Extend native element props: all <button> props + our own
type ButtonProps = ComponentProps<"button"> & { variant?: "primary" | "ghost"; loading?: boolean };

export function Button({ variant = "primary", loading = false, children, ...rest }: ButtonProps) {
  return (
    <button className={`btn btn-${variant}`} disabled={loading || rest.disabled} {...rest}>
      {loading ? "Please wait..." : children}
    </button>
  );
}
TypeUse for
ReactNodeanything renderable: JSX, string, number, null, arrays
ReactElementexactly one JSX element
ComponentProps<"input">all props of a native element (or of another component)
PropsWithChildren<P>adds children?: ReactNode to P