Learn about me or read more of my blog
Written by Giulio Canti on 10 Sep 2014
Definitely yes. Adding propTypes to all the components improves development speed, testability and documents the shape of the consumed data. After two months who can’t even remember which property feeds which component?
But in order to become an ubiquitous practice, the syntax used to express the constraints must be simple and expressive, otherwise chances are that laziness leads to undocumented and opaque components.
The tools provided by React are good, but when the domain models become more complex and you want express fine-grained constraints you can hit a wall.
Say you are writing a component that represents the Alert
component of Bootstrap and you want to check its props.
There is already a fine implementation, so let’s assume that as base. Here a short list of handled props:
info
, warning
, success
, danger
large
, medium
, small
, xsmall
onDismiss
must be specified toovar pt = require('react').PropTypes;
var propTypes = {
bsStyle: pt.oneOf(['info', 'warning', 'success', 'danger']).isRequired,
bsSize: pt.oneOf(['large', 'medium', 'small', 'xsmall']),
onDismiss: pt.func,
dismissAfter: function(props) {
var n = props.dismissAfter;
// dismissAfter is optional
if (n != null) {
// dismissAfter should be a positive integer
if (typeof n !== 'number' || n !== parseInt(n, 10) || n < 0) {
return new Error('Invalid `dismissAfter` supplied to `Alert`' +
', expected a positive integer');
}
// if specified then `onDismiss` must be specified too
if (typeof props.onDismiss !== 'function') {
return new Error('Invalid `onDismiss` supplied to `Alert`' +
', expected a func when `dismissAfter` is specified');
}
}
}
};
Notes:
var t = require('tcomb-react');
var BsStyle = t.enums.of('info warning success danger');
var BsSize = t.enums.of('large medium small xsmall');
// a predicate is a function with signature (x) -> boolean
function predicate(n) { return n === parseInt(n, 10) && n >= 0; }
var PositiveInt = t.subtype(t.Num, predicate);
function globalPredicate(x) {
return !( !t.Nil.is(x.dismissAfter) && t.Nil.is(x.onDismiss) );
}
var AlertProps = t.subtype(t.struct({
bsStyle: BsStyle,
bsSize: t.maybe(BsSize), // `maybe` means optional
onDismiss: t.maybe(t.Func),
dismissAfter: t.maybe(PositiveInt)
}, globalPredicate);
// `bind` returns a proxy component with the same interface of the original component
// but with asserts included. In production you can choose to switch to the original one
var SafeAlert = t.react.bind(Alert, AlertProps, {strict: true});
Features:
.isRequired
{strict: true}
means all unspecified props are not allowedsubtype
syntaxFor a complete implementation of the ideas exposed in this post see the tcomb-react library on GitHub.
If you want to see tcomb-react in action, check out the playground of tcomb-react-bootstrap, an attempt to add a type checking layer to the components of react-bootstrap.
Desc | React | Proposed syntax | </thead>||
---|---|---|---|---|
optional prop | A | maybe(A) | </tr>||
required prop | A.isRequired | A | </tr>||
primitives |
array bool func number object string ✘ ✘ ✘ ✘ |
Arr Bool Func Num Obj Str Nil - null, undefined Re - RegExp Dat - Date Err - Error |
</tr>
||
tuples | ✘ | tuple([A, B]) | </tr>||
subtypes | ✘ | subtype(A, predicate) | </tr>||
all | any | Any | </tr>||
lists | arrayOf(A) | list(A) | </tr>||
components | component | Component | </tr>||
instance | instanceOf(A) | A | </tr>||
dictionaries | objectOf(A) | Dict(A) | </tr>||
enums | oneOf(['a', 'b']) | enums.of('a b') | </tr>||
unions | oneOfType([A, B]) | union([A, B]) | </tr>||
renderable | renderable | Renderable | </tr>||
duck typing | shape | struct | </tr> </tbody> </table>