237

Does using React.PropTypes make sense in a TypeScript React Application or is this just a case of "belt and suspenders"?

Since the component class is declared with a Props type parameter:

interface Props {
    // ...
}
export class MyComponent extends React.Component<Props, any> { ... }

is there any real benefit to adding

static propTypes {
    myProp: React.PropTypes.string
}

to the class definition?

Ralph
  • 31,584
  • 38
  • 145
  • 282

6 Answers6

331

Typescript and PropTypes serve different purposes. Typescript validates types at compile time, whereas PropTypes are checked at runtime.

Typescript is useful when you are writing code: it will warn you if you pass an argument of the wrong type to your React components, give you autocomplete for function calls, etc.

PropTypes are useful when you test how the components interact with external data, for example when you load JSON from an API. PropTypes will help you debug (when in React's Development mode) why your component is failing by printing helpful messages like:

Warning: Failed prop type: Invalid prop `id` of type `number` supplied to `Table`, expected `string`

Even though it may seem like Typescript and PropTypes do the same thing, they don't actually overlap at all. But it is possible to automatically generate PropTypes from Typescript so that you don't have to specify types twice, see for example:

afonsoduarte
  • 11,588
  • 3
  • 16
  • 10
  • 2
    Do the propTypes and Typescript types get out of sync easily? Has anyone had maintenance experience to tell us? – Leonardo Apr 27 '19 at 13:38
  • 38
    This is the correct answer! PropTypes (run-time) are not the same as static type checking (compile time). Hence using both is not a 'pointless exercise'. – hans May 31 '19 at 16:55
  • 1
    Here is a nice explanation on how static types can be inferred from PropTypes: https://dev.to/busypeoples/notes-on-typescript-inferring-react-proptypes-1g88 – hans May 31 '19 at 17:20
  • Runtime vs compile time doesn't make sense when you have vue cli with hot reload and eslint. Which generates errors on save. – Julia Mar 24 '20 at 15:30
  • 4
    @Julia, hot reload has nothing in common with runtime. Even with hot reload you will have no clue what will be really returned by api – Kostya Tresko Apr 16 '20 at 08:03
  • 1
    How do you get proptypes generated for functions (hooks) with ts-loader? the ts-react-loader plugin doesn't seem to support function components, and the babel plugin requires to setup babel, which I'd like to avoid. – Adrien Lemaire May 15 '20 at 01:34
  • 11
    "Typescript and PropTypes serve different purposes. Typescript validates types at compile time, whereas PropTypes are checked at runtime." --- Compile-time and runtime are not different _purposes_. They're different kinds for the _same_ purpose, and compile time is just superior. – sarimarton Mar 09 '21 at 14:09
162

There's usually not much value to maintaining both your component props as TypeScript types and React.PropTypes at the same time.

Here are some cases where it is useful to do so:

  • Publishing a package such as a component library that will be used by plain JavaScript.
  • Accepting and passing along external input such as results from an API call.
  • Using data from a library that may not have adequate or accurate typings, if any.

So, usually it's a question of how much you can trust your compile time validation.

Newer versions of TypeScript can now infer types based on your React.PropTypes (PropTypes.InferProps), but the resulting types can be difficult to use or refer to elsewhere in your code.

Joel Day
  • 1,917
  • 1
  • 16
  • 12
  • 1
    Could you please explain the first statement? – vehsakul Oct 10 '17 at 22:07
  • 6
    @vehsakul Sorry, for clarification, if you're writing a package that will be installed by developers who are not using TypeScript, they still need PropTypes in order to get errors at run-time. If your project is only for yourself/other TypeScript projects, the TypeScript interfaces for your props are enough because the project simply won't build. – Joel Day Oct 13 '17 at 16:36
  • 1
    This is a POC that adds PropTypes from typescript interfaces on Webpack level https://github.com/grncdr/ts-react-loader#what-it-does – borN_free Feb 28 '18 at 09:38
  • I want a oneOfType -- optionalUnion: PropTypes.oneOfType([ PropTypes.string, PropTypes.number, PropTypes.instanceOf(Message) ]), -- typescript has union types, but they don't quite give me the same thing – Mz A Nov 14 '18 at 06:54
  • 1
    I've published a library that does this as well: https://github.com/joelday/ts-proptypes-transformer It's implemented as a TypeScript compiler transform and it produces accurate propTypes for deep generics, unions, etc. There are some rough edges, so any contributions would be wonderful. – Joel Day Nov 22 '18 at 20:12
  • I wish it was possible to avoid duplicating that in every Component, there should be a better way to define it once so it's valid for both proptypes and typing. – Vadorequest Nov 06 '19 at 22:27
  • This isn't true at all. Just because it compiles doesn't mean it does the same as PropTypes. I can still optionally access a string in a prop to pass null and then `isRequired` will flag it as not set at runtime. – Luke Nov 21 '20 at 17:33
  • @Luke It's a question of how much you can or want to trust your compile-time validation. I outlined the most useful cases for run-time validation. That's the tradeoff. – Joel Day Apr 24 '21 at 19:09
34

As @afonsoduarte said.

I'd just add that you can also generate Typescript types from PropTypes like this:

const propTypes = {
  input: PropTypes.shape({
    id: PropTypes.number.isRequired,
    name: PropTypes.string.isRequired,
  }),
};

type MyComponentProps = PropTypes.InferProps<typeof propTypes>;

const MyComponent: FunctionComponent<MyComponentProps > = (props) => {
  // ...
}

MyComponent.propTypes = propTypes;
thisismydesign
  • 21,553
  • 9
  • 123
  • 126
  • 1
    this seems like a great solution could you please explain if their might be any issues with inferring typescript types from prop types if any, thank you (I'm very new to typescript) – maroof shittu Feb 22 '21 at 23:52
  • 2
    @maroofshittu I've been using this on my strict TS projects and was working fine including for complex scenarios. – thisismydesign Jun 08 '21 at 12:47
6

I guess that in some messy situations where the type of the props can't be inferred at compile time, then it would be useful to see any warnings generated from using propTypes at run time.

One such situation would be when processing data from an external source for which type definitions are not available, such as an external API beyond your control. For internal APIs, I think that is worth the effort to write (or better, generate) type definitions, if they are not already available.

Other than that, I don't really see any benefit (which I why I've never used it personally).

Tom Fenech
  • 72,334
  • 12
  • 107
  • 141
  • 9
    PropTypes validation does also make sense to validate data structures loaded dymanically (coming from server via AJAX). PropTypes is runtime validation, and thus can really help to debug stuff. As problems will output clear and human-friendly messages. – e1v Aug 14 '18 at 22:23
  • so prop types would be helpful in debugging if the app breaks because of some dynamic data not following the agreed upon contract (but only in development mode) – gaurav5430 Feb 24 '22 at 17:32
6

"InferPropTypes" from @types/prop-types can be used to create type definitions from PropTypes definitions. check the below example

import React from "react";
import PropTypes, { InferProps } from "prop-types";

const ComponentPropTypes = {
    title: PropTypes.string.isRequired,
    createdAt: PropTypes.instanceOf(Date),
    authorName: PropTypes.string.isRequired,
};

type ComponentTypes = InferProps<typeof ComponentPropTypes>;
const MyComponent = ({ authorName, createdAt, title }: ComponentTypes) => {
    return <span>Blog Card</span>;
};

MyComponent.propTypes = ComponentPropTypes;
export default MyComponent;
prince kumar
  • 71
  • 1
  • 1
0

I recently used Proptypes and TS when bridging native code. The project is written in TypeScript on the React side, and I abstract away my native component on the React side in its own file. There was no need for worrying about PropTypes had this not been in its own file since I am already validating the data via TypeScript.

The PropTypes are used to handle external data coming in from Swift on an event callback. I tried using TypeScript here instead of PropTypes, but I was having issues with referencing the React components.

Ultimately, it was easier to implement PropTypes and doesn't seem to have drawbacks, since data validation at runtime worked perfectly fine.

Please refer to the code here for more detail:

//CoreView.js
import React, {Component} from 'react';
import PropTypes from 'prop-types';

import {requireNativeComponent, UIManager, findNodeHandle} from 'react-native';

const COMPONENT_NAME = 'CoreView';
const RNCoreView = requireNativeComponent(COMPONENT_NAME);

export default class CoreView extends Component {
  static propTypes = {
    label: PropTypes.array,
    onUpdate: PropTypes.func,
  };
  _onUpdate = event => {
    if (this.props.onUpdate) {
      this.props.onUpdate(event.nativeEvent);
    }
  };
  render() {
    const {label, style} = this.props;
    return (
      <RNCoreView
        style={style}
        label={label}
        onUpdate={this._onUpdate}
        ref={ref => (this.ref = ref)}
      />
    );
  }
  update = (...args) => {
    UIManager.dispatchViewManagerCommand(
      findNodeHandle(this.ref),
      UIManager[COMPONENT_NAME].Commands.obtainLabelData,
      [...args],
    );
  };
}

And on the native side:

//CoreViewManager.m
#import <Foundation/Foundation.h>

#import "React/RCTViewManager.h"
@interface RCT_EXTERN_MODULE(CoreViewManager, RCTViewManager)

//Allow React to send data as props
RCT_EXPORT_VIEW_PROPERTY(onUpdate, RCTDirectEventBlock)

RCT_EXTERN_METHOD(
  obtainLabelData:(nonnull NSNumber *)node
  imageLocation:(nonnull NSString *)imageLocation
)

@end

as well as...

import Foundation

@available(iOS 11.0, *)
@objc(CoreViewManager)
class CoreViewManager: RCTViewManager {
  override func view() -> UIView! {
    return CoreView()
  }
  
  @objc func obtainLabelData(_ node: NSNumber, imageLocation: NSString!) {
      
      DispatchQueue.main.async {
        let component = self.bridge.uiManager.view(
          forReactTag: node
        ) as! CoreView
        component.update(value: imageLocation)
      }
    }
}
Shah
  • 2,126
  • 1
  • 16
  • 21