Destructuring props in React and TypeScript: a cheat sheet

I will be honest: destructuring props in TypeScript and React is something I have tripped over more than once. The syntax options are numerous, the interface vs type debate never seems to have a definitive answer, and there is one specific mistake that looks completely reasonable but means something different entirely in JavaScript. This is my cheat sheet.


Props without TypeScript

In plain JavaScript React, you can destructure props directly in the function signature without specifying any types:

function Avatar({ name, age }) {
  return <div>{name}, {age}</div>;
}

This works fine but gives you no type safety. TypeScript adds the ability to describe exactly what shape the props object should be, catching mismatches at compile time rather than at runtime.


Typing props in TypeScript

There are three main ways to type component props in TypeScript. All three are valid and you will encounter all of them in the wild.

1. Inline anonymous type

You can define the type directly in the function signature as an anonymous object type:

function Avatar({ name, age }: { name: string; age: number }) {
  return <div>{name}, {age}</div>;
}

This is concise for simple components with few props, but becomes unwieldy as the number of props grows. It also cannot be reused across multiple components.

2. Using an interface

An interface defines a named contract that the props object must follow:

interface AvatarProps {
  name: string;
  age: number;
}

function Avatar({ name, age }: AvatarProps) {
  return <div>{name}, {age}</div>;
}

Interfaces can be extended using the extends keyword, which makes them useful when components share a common set of props:

interface BaseProps {
  id: string;
}

interface AvatarProps extends BaseProps {
  name: string;
  age: number;
}

3. Using a type alias

A type alias works almost identically to an interface for defining props:

type AvatarProps = {
  name: string;
  age: number;
}

function Avatar({ name, age }: AvatarProps) {
  return <div>{name}, {age}</div>;
}

Type aliases use & for composition rather than extends:

type BaseProps = {
  id: string;
}

type AvatarProps = BaseProps & {
  name: string;
  age: number;
}

Interface vs type: is there a definitive answer?

No, and if you have been searching for one you can stop. Both interface and type work for defining component props in TypeScript and are largely interchangeable for this use case. The TypeScript team itself has softened its original guidance over the years as the two have converged in capability.

What you will find instead are conventions, some held strongly by individual developers and some enforced at a team or company level. The most commonly cited convention is:

  • Use interface for component props and object shapes, particularly when you may need to extend them
  • Use type for union types, utility type compositions, and cases where you need features that interfaces do not support

A practical example of this distinction:

// type is natural here: a union of specific string values
type ButtonVariant = 'primary' | 'secondary' | 'danger';

// interface is natural here: a contract for component props
interface ButtonProps {
  label: string;
  variant: ButtonVariant;
  onClick: () => void;
}

But this is a convention, not a rule. Many teams use type for everything, many use interface for everything, and both approaches produce working TypeScript. If you are joining an existing codebase, follow whatever convention is already in use. If you are starting fresh, pick one approach and apply it consistently.


The pitfall: do not annotate types when destructuring

This is the mistake I keep coming back to this cheat sheet for. It looks reasonable but it does not do what you might expect.

You might be tempted to write this:

// Do NOT do this
function Avatar({ name: string, age: number }) {
  return <div>{name}, {age}</div>;
}

This is not a TypeScript type annotation. In JavaScript, the destructuring syntax { name: string } already has a meaning: it means “take the name property from the object and assign it to a variable called string“. You have not typed the name prop as a string. You have renamed it to a variable called string and the original name variable no longer exists inside the function.

TypeScript will not always catch this immediately, which makes it a particularly confusing bug to track down.

The correct approach is to apply the type annotation to the entire props parameter, not to the individual destructured values:

// Correct: type annotation on the props parameter
function Avatar({ name, age }: { name: string; age: number }) {
  return <div>{name}, {age}</div>;
}

// Also correct: using an interface or type alias
function Avatar({ name, age }: AvatarProps) {
  return <div>{name}, {age}</div>;
}

The type annotation always goes after the closing brace of the destructured parameter, not inside it.


Quick reference

ApproachSyntaxWhen to use
Inline anonymous type({ name, age }: { name: string; age: number })Simple components with few props
Interface({ name, age }: AvatarProps)Props that may be extended or reused
Type alias({ name, age }: AvatarProps)Props involving unions or complex compositions

Further reading


Posted

in

, , ,

by

Tags: