Type checking for React components


Keywords
tcomb, react, react-component
License
MIT
Install
npm install tcomb-react@0.1.0

Documentation

build status dependency status npm downloads

Features

  • by default props are required, a saner default since it's quite easy to forget .isRequired
  • checks for unwanted additional props
  • documentation (types and comments) can be automatically extracted
  • additional fine grained type checks, nestable at arbitrary level
  • builds on tcomb, tcomb-validation, tcomb-doc libraries

Compatibility

tcomb-react has been tested and found working on the following targets. The list is not exhaustive and tcomb-react will probably work on other versions that haven't been listed.

React: ^0.13.0, ^0.14.0, ^15.0.0

Prop types

The @props decorator (ES7)

For an equivalent implementation in ES5, or for Stateless Components, see the propTypes function below.

Signature

type Props = {[key: string]: TcombType};

type PropsType = TcombStruct | TcombInterface;

type Type = Props | PropsType | Refinement<PropsType>;

type Options = {
  strict?: boolean // default true
};

@props(type: Type, options?: Options)

where

  • type can be a map string -> TcombType, a tcomb struct, a tcomb interface, a refinement of a tcomb struct / interface, a refinement of a tcomb interface
  • options:
    • strict: boolean (default true) if true checks for unwanted additional props

Example (ES7)

import t from 'tcomb'
import { props } from 'tcomb-react'

const Gender = t.enums.of(['Male', 'Female'], 'Gender')
const URL = t.refinement(t.String, (s) => s.startsWith('http'), 'URL')

@props({
  name: t.String,             // a required string
  surname: t.maybe(t.String), // an optional string
  age: t.Number,              // a required number
  gender: Gender,             // an enum
  avatar: URL                 // a refinement
})
class Card extends React.Component {

  render() {
    return (
      <div>
        <p>{this.props.name}</p>
        ...
      </div>
    )
  }

}

Unwanted additional props

By default tcomb-react checks for unwanted additional props:

@props({
  name: t.String
})
class Person extends React.Component {

  render() {
    return (
      <div>
        <p>{this.props.name}</p>
      </div>
    )
  }

}

...

<Person name="Giulio" surname="Canti" />

Output

Warning: Failed propType: [tcomb] Invalid additional prop(s):

[
  "surname"
]

supplied to Person.

Note. You can opt-out passing the option argument { strict: false }.

The propTypes function

Signature

Same as @props.

Stateless Component Example

import { propTypes } from 'tcomb-react'

const MyComponentProps = t.interface({
  name: t.String,
});

const MyComponent = (props) => (
  <div />
);
MyComponent.propTypes = propTypes(MyComponentProps);

ES5 React.createClass Example

var t = require('tcomb');
var propTypes = require('tcomb-react').propTypes;

var Gender = t.enums.of(['Male', 'Female'], 'Gender');
var URL = t.refinement(t.String, function (s) { return s.startsWith('http'); }, 'URL');

var Card = React.createClass({

  propTypes: propTypes({
    name: t.String,             // a required string
    surname: t.maybe(t.String), // an optional string
    age: t.Number,              // a required number
    gender: Gender,             // an enum
    avatar: URL                 // a refinement
  }),

  render: function () {
    return (
      <div>
        <p>{this.props.name}</p>
        ...
      </div>
    );
  }

});

How it works

The @props decorator sets propTypes on the target component to use a custom validator function built around tcomb types for each specified prop.

For example, the following:

const URL = t.refinement(t.String, (s) => s.startsWith('http'), 'URL');

@props({
  name: t.String,
  url: URL,
})
class MyComponent extends React.Component {
  // ...
}

is roughly equivalent to:

const URL = t.refinement(t.String, (s) => s.startsWith('http'), 'URL');

class MyComponent extends React.Component {
  // ...
}
MyComponent.propTypes = {
  name: function(props, propName, componentName) {
    if (!t.validate(props[propName], t.String).isValid()) {
      return new Error('...');
    }
  },
  url: function(props, propName, componentName) {
    if (!t.validate(props[propName], URL).isValid()) {
      return new Error('...');
    }
  },
}

The babel plugin

Using babel-plugin-tcomb you can express propTypes as Flow type annotations:

import React from 'react'
import ReactDOM from 'react-dom'
import type { $Refinement } from 'tcomb'
import { props } from 'tcomb-react'

type Gender = 'Male' | 'Female';

const isUrl = (s) => s.startsWith('http')
type URL = string & $Refinement<typeof isUrl>;

type Props = {
  name: string,
  surname: ?string,
  age: number,
  gender: Gender,
  avatar: URL
};

@props(Props)
class Card extends React.Component {

  render() {
    return (
      <div>
        <p>{this.props.name}</p>
        ...
      </div>
    )
  }

}

Extract documentation from your components

The parse function

Given a path to a component file returns a JSON / JavaScript blob containing props types, default values and comments.

Signature

(path: string | Array<string>) => Object

Example

Source

import t from 'tcomb'
import { props } from 'tcomb-react'

/**
 * Component description here
 * @param name - name description here
 * @param surname - surname description here
 */

@props({
  name: t.String,             // a required string
  surname: t.maybe(t.String)  // an optional string
})
export default class Card extends React.Component {

  static defaultProps = {
    surname: 'Canti'          // default value for surname prop
  }

  render() {
    return (
      <div>
        <p>{this.props.name}</p>
        <p>{this.props.surname}</p>
      </div>
    )
  }
}

Usage

import parse from 'tcomb-react/lib/parse'
const json = parse('./components/Card.js')
console.log(JSON.stringify(json, null, 2))

Output

{
  "name": "Card",
  "description": "Component description here",
  "props": {
    "name": {
      "kind": "irreducible",
      "name": "String",
      "required": true,
      "description": "name description here"
    },
    "surname": {
      "kind": "irreducible",
      "name": "String",
      "required": false,
      "defaultValue": "Canti",
      "description": "surname description here"
    }
  }
}

Note. Since parse uses runtime type introspection, your components should be requireable from your script (you may be required to shim the browser environment).

Parsing multiple components

import parse from 'tcomb-react/lib/parse'
import path from 'path'
import glob from 'glob'

function getPath(file) {
  return path.resolve(process.cwd(), file);
}

parse(glob.sync('./components/*.js').map(getPath));

The toMarkdown function

Given a JSON / JavaScript blob returned by parse returns a markdown containing the components documentation.

Signature

(json: Object) => string

Example

Usage

import parse from 'tcomb-react/lib/parse'
import toMarkdown from 'tcomb-react/lib/toMarkdown'
const json = parse('./components/Card.js')
console.log(toMarkdown(json));

Output

## Card

Component description here

**Props**

- `name: String` name description here
- `surname: String` (optional, default: `"Canti"`) surname description here

Augmented pre-defined types

tcomb-react exports some useful pre-defined types:

  • ReactElement
  • ReactNode
  • ReactChild
  • ReactChildren

Example

import { props, ReactChild } from 'tcomb-react';

@props({
  children: ReactChild // only one child is allowed
})
class MyComponent extends React.Component {

  render() {
    return (
      <div>
        {this.props.children}
      </div>
    );
  }

}

Support for babel-plugin-tcomb

The following types for Flow are exported:

  • ReactElementT
  • ReactNodeT
  • ReactChildT
  • ReactChildrenT

Comparison table

Type React tcomb-react
array array Array
boolean bool Boolean
functions func Function
numbers number Number
objects object Object
strings string String
all any Any
required prop T.isRequired T
optional prop T maybe(T)
custom types ✘ βœ“
tuples ✘ tuple([T, U, ...])
lists arrayOf(T) list(T)
instance instanceOf(A) T
dictionaries objectOf(T) dict(T, U) (keys are checked)
enums oneOf(['a', 'b']) enums.of('a b')
unions oneOfType([T, U]) union([T, U])
duck typing shape interface
react element element ReactElement
react node node ReactNode
react child ✘ ReactChild
react children ✘ ReactChildren