React SME Cookbook
All FAQs

Search Documentation

Search across all documentation pages

typescriptreactpropsinterfaceschildren

Typing Props

Recipe

Define clear, type-safe prop contracts for your React components using TypeScript interfaces and type aliases. Cover required props, optional props, children, and common prop patterns.

Working Example

// Basic props with required and optional fields
type ButtonProps = {
  label: string;
  variant?: "primary" | "secondary" | "danger";
  disabled?: boolean;
  onClick: () => void;
};
 
export function Button({ label, variant = "primary", disabled = false, onClick }: ButtonProps) {
  return (
    <button className={`btn btn-${variant}`} disabled={disabled} onClick={onClick}>
      {label}
    </button>
  );
}
// Typing children
type CardProps = {
  title: string;
  children: React.ReactNode;
};
 
export function Card({ title, children }: CardProps) {
  return (
    <div className="card">
      <h2>{title}</h2>
      <div className="card-body">{children}</div>
    </div>
  );
}
// Render prop pattern
type DataListProps<T> = {
  items: T[];
  renderItem: (item: T, index: number) => React.ReactNode;
};
 
export function DataList<T>({ items, renderItem }: DataListProps<T>) {
  return <ul>{items.map((item, i) => <li key={i}>{renderItem(item, i)}</li>)}</ul>;
}
// Usage
<Button label="Submit" onClick={() => console.log("clicked")} />
<Card title="Welcome"><p>Hello world</p></Card>
<DataList items={["a", "b"]} renderItem={(item) => <span>{item}</span>} />

Deep Dive

How It Works

  • Props types act as a contract between the component and its consumers. TypeScript enforces that every required prop is supplied and every value matches the expected type.
  • React.ReactNode is the broadest children type. It accepts strings, numbers, JSX elements, arrays, fragments, null, and undefined.
  • React.ReactElement is narrower than ReactNode. Use it when you specifically need a JSX element (not a string or number).
  • Default values in destructuring (e.g., variant = "primary") work seamlessly with optional props. TypeScript infers the narrowed type inside the function body.
  • Union literal types like "primary" | "secondary" give you autocomplete and catch typos at compile time.

Variations

Extending HTML element props:

type InputProps = React.ComponentPropsWithoutRef<"input"> & {
  label: string;
  error?: string;
};
 
export function Input({ label, error, ...rest }: InputProps) {
  return (
    <div>
      <label>{label}</label>
      <input {...rest} />
      {error && <span className="error">{error}</span>}
    </div>
  );
}

Props with component injection:

type LayoutProps = {
  as?: React.ElementType;
  children: React.ReactNode;
  className?: string;
};
 
export function Layout({ as: Component = "div", children, className }: LayoutProps) {
  return <Component className={className}>{children}</Component>;
}

TypeScript Notes

  • Use type for props that use unions or intersections. Use interface when you need extends or want declaration merging.
  • React.PropsWithChildren<T> is a shorthand that adds children?: React.ReactNode to your type.
  • React.ComponentPropsWithRef<"div"> includes the ref prop; ComponentPropsWithoutRef<"div"> excludes it.

Gotchas

  • Using React.FC adds an implicit children prop in older React types (pre-18). In React 18+ types, React.FC no longer includes children implicitly.
  • Typing children as JSX.Element will reject strings, numbers, and arrays. Use React.ReactNode unless you have a specific reason.
  • Spreading ...rest onto a DOM element without filtering custom props causes React warnings about unknown DOM attributes.
  • Forgetting to export your props type makes it impossible for consumers to reuse or extend it.

Alternatives

ApproachProsCons
type aliasSupports unions, intersections, mapped typesNo declaration merging
interfaceExtendable, familiar OOP patternCannot express union types directly
React.FC<Props>Explicit return type annotationVerbose, no generic component support
Inline prop typesQuick for throwaway componentsHard to reuse or export
PropsWithChildrenConvenient children shorthandHides the children prop from readers

FAQs

Should you use type or interface for component props?
  • Use type when you need unions, intersections, or mapped types.
  • Use interface when you need extends or declaration merging.
  • For most React components, type is sufficient and more flexible.
What is the difference between React.ReactNode and React.ReactElement for children?
  • ReactNode accepts strings, numbers, JSX elements, arrays, fragments, null, and undefined.
  • ReactElement only accepts JSX elements (not strings or numbers).
  • Use ReactNode unless you specifically need to restrict children to JSX elements.
How do you extend native HTML element props for a custom component?
type InputProps = React.ComponentPropsWithoutRef<"input"> & {
  label: string;
  error?: string;
};
 
function Input({ label, error, ...rest }: InputProps) {
  return (
    <div>
      <label>{label}</label>
      <input {...rest} />
      {error && <span>{error}</span>}
    </div>
  );
}
  • Use ComponentPropsWithoutRef<"element"> to get all native props.
  • Intersect with your custom props using &.
Gotcha: What happens if you spread ...rest onto a DOM element without filtering custom props?
  • React will warn about unknown DOM attributes.
  • Custom props like label or error get passed to the HTML element.
  • Destructure custom props out before spreading the rest.
How do default values work with optional props in TypeScript?
type ButtonProps = {
  variant?: "primary" | "secondary";
};
 
function Button({ variant = "primary" }: ButtonProps) {
  // TypeScript narrows variant to "primary" | "secondary" (not undefined)
}
  • Mark the prop as optional with ? in the type.
  • Provide the default in the destructuring pattern.
  • TypeScript infers the narrowed type inside the function body.
What is React.PropsWithChildren<T> and when should you use it?
  • A shorthand that adds children?: React.ReactNode to your type.
  • Convenient but hides the children prop from readers of the type definition.
  • Prefer explicit children: React.ReactNode in the type for clarity.
Gotcha: Does React.FC include children in its type?
  • In React 18+ types, React.FC no longer includes children implicitly.
  • In older React types (pre-18), it added an implicit children prop.
  • If you need children, declare it explicitly in your props type.
How do you type a render prop?
type DataListProps<T> = {
  items: T[];
  renderItem: (item: T, index: number) => React.ReactNode;
};
  • The render prop is a function that returns React.ReactNode.
  • Use a generic to type the item parameter based on the data.
What is the as prop pattern for polymorphic components?
type LayoutProps = {
  as?: React.ElementType;
  children: React.ReactNode;
};
 
function Layout({ as: Component = "div", children }: LayoutProps) {
  return <Component>{children}</Component>;
}
  • React.ElementType accepts string tags ("div", "span") and component types.
  • The default is typically a semantic HTML element.
Why should you export your props type?
  • Consumers may need to reuse or extend your component's props type.
  • Without the export, they cannot build wrapper components or extract prop subsets.
  • Always export props types for shared or library components.