- 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
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
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
typecan be a mapstring -> TcombType, atcombstruct, atcombinterface, a refinement of atcombstruct / interface, a refinement of atcombinterfaceoptions:strict: boolean(defaulttrue) iftruechecks 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 }.
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>
);
}
});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('...');
}
},
}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>
)
}
}Given a path to a component file returns a JSON / JavaScript blob containing props types, default values and comments.
Signature
(path: string | Array<string>) => ObjectExample
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));Given a JSON / JavaScript blob returned by parse returns a markdown containing the components documentation.
Signature
(json: Object) => stringExample
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
tcomb-react exports some useful pre-defined types:
ReactElementReactNodeReactChildReactChildren
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>
);
}
}The following types for Flow are exported:
ReactElementTReactNodeTReactChildTReactChildrenT
| 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 |