NexusCS

React PropTypes

React
A quick reference to the prop-types package for runtime type checking in React components.
featured

Getting started

Introduction

PropTypes provide runtime type checking for React component props. They warn developers when incorrect prop types are passed, helping catch bugs during development. PropTypes are stripped in production builds for performance.

Installation

npm install prop-types

Basic usage

import PropTypes from "prop-types";

function UserCard({ name, age, email }) {
  return (
    <div>
      <h2>{name}</h2>
      <p>Age: {age}</p>
      <p>Email: {email}</p>
    </div>
  );
}

UserCard.propTypes = {
  name: PropTypes.string.isRequired,
  age: PropTypes.number,
  email: PropTypes.string,
};

PropTypes are defined as a static property on the component. Use .isRequired to make props mandatory.

Primitive types

Basic validators

Validator Description
PropTypes.string String value
PropTypes.number Number value
PropTypes.bool Boolean value
PropTypes.func Function
PropTypes.symbol Symbol
PropTypes.bigint BigInt value

Example

MyComponent.propTypes = {
  title: PropTypes.string,
  count: PropTypes.number,
  isActive: PropTypes.bool,
  onClick: PropTypes.func,
  id: PropTypes.symbol,
  largeValue: PropTypes.bigint,
};

Required props

MyComponent.propTypes = {
  name: PropTypes.string.isRequired,
  age: PropTypes.number.isRequired,
  callback: PropTypes.func.isRequired,
};

Chain .isRequired after any validator to make the prop mandatory.

Complex types

React types

Validator Description
PropTypes.node Anything renderable (string, number, element, array)
PropTypes.element React element
PropTypes.elementType React component type
PropTypes.instanceOf(Class) Instance of a class

Collections

Validator Description
PropTypes.array Any array
PropTypes.object Any object
PropTypes.arrayOf(type) Array of specific type
PropTypes.objectOf(type) Object with values of specific type

Example

import Message from "./Message";

MyComponent.propTypes = {
  // Renderable content
  children: PropTypes.node,

  // React element
  icon: PropTypes.element,

  // Component type
  component: PropTypes.elementOf,

  // Class instance
  message: PropTypes.instanceOf(Message),

  // Collections
  tags: PropTypes.arrayOf(PropTypes.string),
  scores: PropTypes.objectOf(PropTypes.number),
};

Enumerables

oneOf - Enum values

MyComponent.propTypes = {
  direction: PropTypes.oneOf(["left", "right", "up", "down"]),

  size: PropTypes.oneOf(["sm", "md", "lg"]),

  status: PropTypes.oneOf(["pending", "success", "error"]),
};

Use oneOf to restrict prop values to a specific set of options.

oneOfType - Union types

MyComponent.propTypes = {
  id: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),

  content: PropTypes.oneOfType([
    PropTypes.string,
    PropTypes.element,
    PropTypes.arrayOf(PropTypes.element),
  ]),

  value: PropTypes.oneOfType([PropTypes.number, PropTypes.instanceOf(Date)]),
};

Use oneOfType when a prop can accept multiple different types.

Shape validation

shape - Object shape

MyComponent.propTypes = {
  user: PropTypes.shape({
    name: PropTypes.string,
    age: PropTypes.number,
    email: PropTypes.string,
  }),
};

Allows extra properties not defined in the shape.

// ✅ Valid - extra "id" property is allowed
<MyComponent
  user={{
    name: "John",
    age: 30,
    id: 123,
  }}
/>

exact - Exact object shape

MyComponent.propTypes = {
  user: PropTypes.exact({
    name: PropTypes.string,
    age: PropTypes.number,
  }),
};

Warns if extra properties are present.

// ⚠️ Warning - extra "id" property not allowed
<MyComponent
  user={{
    name: "John",
    age: 30,
    id: 123,
  }}
/>

Nested shapes

MyComponent.propTypes = {
  address: PropTypes.shape({
    street: PropTypes.string,
    city: PropTypes.string,
    coordinates: PropTypes.shape({
      lat: PropTypes.number.isRequired,
      lng: PropTypes.number.isRequired,
    }),
  }),
};

Shapes can be nested to validate complex object structures.

Array of shapes

MyComponent.propTypes = {
  users: PropTypes.arrayOf(
    PropTypes.shape({
      id: PropTypes.number.isRequired,
      name: PropTypes.string.isRequired,
      email: PropTypes.string,
    }),
  ).isRequired,
};

Combine arrayOf with shape to validate arrays of objects.

Custom validators

Basic custom validator

MyComponent.propTypes = {
  email: function (props, propName, componentName) {
    const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;

    if (!emailRegex.test(props[propName])) {
      return new Error(
        `Invalid prop \`${propName}\` supplied to \`${componentName}\`. ` +
          `Expected a valid email address.`,
      );
    }
  },
};

Custom validators receive props, propName, and componentName. Return an Error object on failure, null on success.

Custom array validator

MyComponent.propTypes = {
  scores: PropTypes.arrayOf(
    function (propValue, key, componentName, location, propFullName) {
      if (propValue[key] < 0 || propValue[key] > 100) {
        return new Error(
          `Invalid prop \`${propFullName}\` supplied to \`${componentName}\`. ` +
            `Score must be between 0 and 100.`,
        );
      }
    },
  ),
};

Array validators receive the array value, key, and additional location info.

Validator rules

  • Return new Error(message) on validation failure
  • Return null (or nothing) on success
  • Don't use console.warn() or console.error()
  • Don't throw errors directly
  • Avoid side effects
  • Keep validators simple (they run on every render in dev mode)

Reusable validators

const emailValidator = (props, propName, componentName) => {
  const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
  if (!emailRegex.test(props[propName])) {
    return new Error(`Invalid email in ${componentName}.${propName}`);
  }
};

Component1.propTypes = { email: emailValidator };
Component2.propTypes = { userEmail: emailValidator };

Extract validators into reusable functions for consistency.

Default props

Function component

function Hello({ name, greeting }) {
  return (
    <div>
      {greeting}, {name}!
    </div>
  );
}

Hello.propTypes = {
  name: PropTypes.string,
  greeting: PropTypes.string,
};

Hello.defaultProps = {
  name: "Stranger",
  greeting: "Hello",
};

Define defaultProps as a static property on function components.

Class component

class Hello extends React.Component {
  static propTypes = {
    name: PropTypes.string,
    greeting: PropTypes.string,
  };

  static defaultProps = {
    name: "Stranger",
    greeting: "Hello",
  };

  render() {
    return (
      <div>
        {this.props.greeting}, {this.props.name}!
      </div>
    );
  }
}

Use static class properties for propTypes and defaultProps.

Default prop behavior

// Uses default
<Hello />
// → name = 'Stranger'

// Uses default (undefined triggers default)
<Hello name={undefined} />
// → name = 'Stranger'

// Does NOT use default (null is a value)
<Hello name={null} />
// → name = null

Only undefined triggers default values. null is treated as an explicit value.

With object destructuring

function Button({ variant = "primary", size = "md", children }) {
  return <button className={`btn-${variant} btn-${size}`}>{children}</button>;
}

// Can still use PropTypes
Button.propTypes = {
  variant: PropTypes.oneOf(["primary", "secondary"]),
  size: PropTypes.oneOf(["sm", "md", "lg"]),
  children: PropTypes.node,
};

You can use ES6 default parameters instead of defaultProps, but they work slightly differently with destructuring.

Component patterns

Children validation

// Single React element required
MyComponent.propTypes = {
  children: PropTypes.element.isRequired,
};

// Any renderable content
MyComponent.propTypes = {
  children: PropTypes.node,
};

// Array of elements
MyComponent.propTypes = {
  children: PropTypes.arrayOf(PropTypes.element),
};

// Specific element type
MyComponent.propTypes = {
  children: PropTypes.instanceOf(MyChildComponent),
};

With React.forwardRef

const FancyButton = React.forwardRef((props, ref) => (
  <button ref={ref} className={props.className}>
    {props.children}
  </button>
));

FancyButton.displayName = "FancyButton";

FancyButton.propTypes = {
  children: PropTypes.node,
  className: PropTypes.string,
};

PropTypes work normally with forwardRef components.

With React.memo

const MemoComponent = React.memo(function MyComponent({ name, age }) {
  return (
    <div>
      {name} is {age} years old
    </div>
  );
});

MemoComponent.propTypes = {
  name: PropTypes.string.isRequired,
  age: PropTypes.number.isRequired,
};

PropTypes are defined on the memoized component wrapper.

Higher-order component

function withAuth(Component) {
  const WithAuth = (props) => {
    // HOC logic...
    return <Component {...props} />;
  };

  WithAuth.propTypes = {
    ...Component.propTypes,
    isAuthenticated: PropTypes.bool,
  };

  return WithAuth;
}

HOCs can inherit and extend PropTypes from wrapped components.

TypeScript migration

Type equivalents

PropTypes TypeScript
PropTypes.string string
PropTypes.number number
PropTypes.bool boolean
PropTypes.func () => void
PropTypes.symbol symbol
PropTypes.array any[]
PropTypes.object object

React types

PropTypes TypeScript
PropTypes.node React.ReactNode
PropTypes.element React.ReactElement
PropTypes.elementType React.ElementType
PropTypes.instanceOf(Message) Message

Advanced types

PropTypes TypeScript
PropTypes.oneOf(['a', 'b']) 'a' | 'b'
PropTypes.oneOfType([...]) Type1 | Type2
PropTypes.arrayOf(PropTypes.number) number[]
PropTypes.objectOf(PropTypes.number) Record<string, number>
PropTypes.shape({...}) interface { ... }
.isRequired Remove ? (non-optional)

Before (PropTypes)

import PropTypes from "prop-types";

function UserCard({ name, age, tags, onEdit }) {
  return <div>{/* ... */}</div>;
}

UserCard.propTypes = {
  name: PropTypes.string.isRequired,
  age: PropTypes.number,
  tags: PropTypes.arrayOf(PropTypes.string).isRequired,
  onEdit: PropTypes.func,
};

After (TypeScript)

interface UserCardProps {
  name: string;
  age?: number;
  tags: string[];
  onEdit?: () => void;
}

function UserCard({ name, age, tags, onEdit }: UserCardProps) {
  return <div>{/* ... */}</div>;
}

Shape to interface

// PropTypes
PropTypes.shape({
  user: PropTypes.shape({
    id: PropTypes.number.isRequired,
    name: PropTypes.string.isRequired,
    email: PropTypes.string,
  }).isRequired,
  permissions: PropTypes.arrayOf(PropTypes.string),
});
// TypeScript
interface User {
  id: number;
  name: string;
  email?: string;
}

interface Props {
  user: User;
  permissions?: string[];
}

Gotchas & tips

Common mistakes

// ❌ Wrong - isRequired before validator
PropTypes.isRequired.string;

// ✅ Correct - isRequired after validator
PropTypes.string.isRequired;

// ❌ Wrong - old React.PropTypes (deprecated)
React.PropTypes.string;

// ✅ Correct - use prop-types package
PropTypes.string;

// ❌ Too permissive
PropTypes.object;

// ✅ Better - use shape
PropTypes.shape({
  id: PropTypes.number,
  name: PropTypes.string,
});

null vs undefined

Component.defaultProps = {
  value: 'default'
}

// undefined → uses default
<Component value={undefined} />
// value = 'default'

// null → does NOT use default
<Component value={null} />
// value = null

Remember: null is an explicit value and won't trigger defaultProps.

Performance notes

  • PropTypes only run in development mode
  • Stripped from production builds (with proper tooling)
  • Run on every render in dev mode
  • Keep custom validators simple
  • Consider migrating to TypeScript for large codebases

PropTypes.any

// ⚠️ Avoid - defeats the purpose
MyComponent.propTypes = {
  data: PropTypes.any,
};

// ✅ Better - be specific
MyComponent.propTypes = {
  data: PropTypes.oneOfType([
    PropTypes.string,
    PropTypes.number,
    PropTypes.object,
  ]),
};

Avoid PropTypes.any - it provides no validation benefit.

Production stripping

Babel plugin

npm install --save-dev babel-plugin-transform-react-remove-prop-types
{
  "env": {
    "production": {
      "plugins": [
        [
          "transform-react-remove-prop-types",
          {
            "removeImport": true
          }
        ]
      ]
    }
  }
}

This plugin removes PropTypes code from production builds to reduce bundle size.

Webpack (DefinePlugin)

// webpack.config.js
module.exports = {
  plugins: [
    new webpack.DefinePlugin({
      "process.env.NODE_ENV": JSON.stringify("production"),
    }),
  ],
};

PropTypes checks are automatically disabled when NODE_ENV === 'production'.

Manual checking

import PropTypes from "prop-types";

// Manual validation (rarely needed)
PropTypes.checkPropTypes(MyComponent.propTypes, props, "prop", "MyComponent");

Manually invoke PropTypes validation. This is rarely needed - React calls this automatically.

Bundle size impact

Bundle Size
With PropTypes ~30KB
Without PropTypes ~0KB

PropTypes add ~30KB to bundles. Always strip in production.

Also see