Type-Checking in React: PropTypes

Chidume Nnamdi 🔥💻🎵🎮
Bits and Pieces
Published in
8 min readFeb 5, 2019

--

In my last post React: Everything about Default Props, we talked about how to set default props value in our React components using the defaultProps property. In this post we will continue with the streak, we will see how to type the props argument.

Tip: Use tools like Bit to make components reusable. Your team can share your components, install them in their projects, suggest updates and build faster.

React component collection with Bit

Problem Statement

Data are passed to React components as inputs, these inputs are captured in the props arguments:

// ES6 class
class SalaryComponent extends React.Component {
constructor(props) {}
static defaultProps = {
annualSalary = 0
}
render() {
return (
<div>
Annual Salary: £{this.props.annualSalary}
Monthly Salary: £{this.props.annualSalary/12}
</div>)
}
}

// Functional component
function SalaryComponent(props) {
return (
<div>
Annual Salary: £{props.annualSalary}
Monthly Salary: £{props.annualSalary/12}
</div>)
}
SalaryComponent.defaultProps = {
annualSalary: 0
}


class ViewSalaryCompoennt extends React.Compoennt {
render() {
return <div><SalaryComponent annualSalary=2000 /></div>
}
}

renders:

Annual Salary: £2000
Monthly Salary: £166

The component takes an annual salary in the props argument. It displays the annual salary passed to it in the annualSalary property in the props object. Then it calculates the monthly salary from the annual salary by dividing the annualSalary by 12. That gets you the monthly salary of the worker.

The components set a default props it will fall back to if the annualSalary is not passed to the props by the parent component.

class ViewSalaryCompoennt extends React.Compoennt {
render() {
return <div><SalaryComponent /></div>
}
}

renders:

Annual Salary: £0
Monthly Salary: £0

Now, note that we are deriving the monthly salary by dividing the annualSalary property by 12. So the annualSalary have to be a Number. A problem arises when a value is other than a Number is passed to annualSalary in the props argument.

class ViewSalaryCompoennt extends React.Compoennt {
render() {
return <div><SalaryComponent annualSalary={{}} /></div>
}
}

renders:

Annual Salary: £NaN
Monthly Salary: £NaN

The annual salary displays NaN and monthly salary displays NaN :(. We don't want our users seeing this.

React provides a way to warn us if a parent component passes in a parameter that is a different type of the expected props. This is done by the use of PropsTypes.

Solution - PropTypes

Type-checking our props will warn us if we pass a type different from the intended props by the child component.

JavaScript is a dynamically typed language, types are determined at runtime.

var f = "me"

Here f is a String but it can be whatever the dev chooses it to be:

var f = "me"
f = 90
f = [90,45]

You see first it is initialized as a String, next it morphs to a Number next to an Array.

In a statically typed language like Java, the type of a variable is determined on initialization, it cannot be changed again:

String f = "me"

f will forever be a String, it can never be changed, trying to change it will throw an error.

React is written in JS and there is no way to check our passed in props type before computation and rendering. We can use TypeScript or Flow to help deal with type-checking but React provided a way to help us type-check our props types in JS. IT is with the PropsTypes.

With PropsTypes we set the type we expect our props it to be. This is done by defining a propsTypes property in our component.

class  ReactComp extends React.Component {}
ReactComp.propsTypes = {}

The propsTypes hold the types that should be passed to the component, React performs a type-check in the props against the types defined in the propsTypes property. If the types don't match, a warning is thrown.

So we will refactor our SalaryComponent to check for Number:

// ES6 class
class SalaryComponent extends React.Component {
constructor(props) {}
static defaultProps = {
annualSalary = 0
}
static propsTypes = {
annualSalary: PropsTypes.number
}
render() {
return (
<div>
Annual Salary: £{this.props.annualSalary}
Monthly Salary: £{this.props.annualSalary/12}
</div>)
}
}

// Functional component
function SalaryComponent(props) {
return (
<div>
Annual Salary: £{props.annualSalary}
Monthly Salary: £{props.annualSalary/12}
</div>)
}
SalaryComponent.defaultProps = {
annualSalary: 0
}
SalaryComponent.propsTypes = {
annualSalary: PropsTypes.number
}

You see we are making sure that the 'annualSalary' prop is a number so it can be divided without errors or "NaN" being displayed. The above tells React to expect a Number in the annualSalary property.

PropsTypes: Installation/Usage

React advises using the prop-types library instead of theirs. They stop supporting it in React since v15.5 and moved it over to a different package.

Note: React.PropTypes has moved into a different package since React v15.5. Please use the proptypes library instead. We provide a codemod script to automate the conversion. - React.js blog

To use propsTypes in our React app we install the props-types library:

npm i props-types

In our React app we import it like this:

import PropTypes from 'prop-types'

// ES6 class
class SalaryComponent extends React.Component {
constructor(props) {}
static defaultProps = {
annualSalary = 0
}
static propsTypes = {
annualSalary: PropsTypes.number
}
render() {
return (
<div>
Annual Salary: £{this.props.annualSalary}
Monthly Salary: £{this.props.annualSalary/12}
</div>)
}
}

// Functional component
function SalaryComponent(props) {
return (
<div>
Annual Salary: £{props.annualSalary}
Monthly Salary: £{props.annualSalary/12}
</div>)
}

SalaryComponent.defaultProps = {
annualSalary: 0
}

SalaryComponent.propsTypes = {
annualSalary: PropsTypes.number
}

PropTypes: Fundamental Validations

You can declare that a prop is a specific JS type. By default, these are all optional.

PropTypes.array: This specifies that the prop is an Array

function TypeComp(props) {
return <div></div>
}
TypeCompo.defaultProps = {

}
TypeComp.propsType = {

}

PropTypes.bool: This specifies that the prop is a Boolean

function TypeComp(props) {
return <div></div>
}
TypeCompo.defaultProps = {

}
TypeComp.propsType = {

}

PropTypes.func: This specifies that the prop is a function

function TypeComp(props) {
return <div></div>
}
TypeCompo.defaultProps = {

}
TypeComp.propsType = {

}

PropTypes.number: This specifies that a prop is a Number

function TypeComp(props) {
return <div></div>
}
TypeCompo.defaultProps = {

}
TypeComp.propsType = {

}

PropTypes.object: This specifies that a prop is an Object

function TypeComp(props) {
return <div></div>
}
TypeCompo.defaultProps = {

}
TypeComp.propsType = {

}

PropTypes.string: This specifies that the prop is a string

function TypeComp(props) {
return <div></div>
}
TypeCompo.defaultProps = {

}
TypeComp.propsType = {

}

PropTypes.symbol: This specifies that the prop is a Symbol

function TypeComp(props) {
return <div></div>
}
TypeCompo.defaultProps = {

}
TypeComp.propsType = {

}

PropTypes: React elements Validation

We can specify that anything renderable by React should be sent: PropTypes.node

OR an React element shoud be passed: PropTypes.element.

PropTypes: instance validation

We can validate the instance of the prop. Here, we specify that the prop must be an instance of a particular class.

You can also declare that a prop is an instance of a class. This uses JS's instanceof operator.

PropTypes.instanceOf(class)

class Model {}

function TypeComp(props) {
return <div>{props.modelProp.name}</div>
}

TypeCompo.defaultProps = {
modelProp:new Model()
}

TypeComp.propsType = {
modelProp: PropTypes.instanceOf(Model)
}

In the above code the modelProp must be an instance of the Model class.

PropTypes: Specific values validation

We can speciffy that our props must be some specific values. That's it must either be this or that or any of these values in a collection.

You can ensure that your prop is limited to specific values by treating it as an enum.

function TypeComp(props) {
return <div></div>
}
TypeCompo.defaultProps = {

}

TypeComp.propsType = {
typeProps: PropTypes.oneOf(['News', 'Photos'])
}

Here the value of typeProps must either be News or Photos.

PropsTypes: Multiple validation

We can specify that our prop could be of any types that are given.

An object that could be one of many types

function TypeComp(props) {
return <div></div>
}
TypeCompo.defaultProps = {
optProp: " " || 0 || new Model()
}

TypeComp.propsType = {
optProp: PropTypes.oneOfType([
PropTypes.string,
PropTypes.number,
PropTypes.instanceOf(Model)
])
}

Here the optProp type can be a string or number or a Model. Any other apart from these will be rejected and error thrown.

PropTypes: Shape and Types validation

We can specify a prop to be of type array and the array values must be of a certain type :)

An array of a certain type

function TypeComp(props) {
return <div></div>
}
TypeCompo.defaultProps = {

}

TypeComp.propsType = {
arrayOfProp: PropTypes.arrayOf(PropTypes.number),
}

The arrayOFProp must be of type array with values of type number. Error will be throw if we pass this:

<TypeComp arrayOfProp={[34,6,"34"]} />

We can specify our prop to be of type object and the property valus of the object must be of a certain type

An object with property values of a certain type optionalObjectOf: PropTypes.objectOf(PropTypes.number)

function TypeComp(props) {
return <div></div>
}
TypeCompo.defaultProps = {

}

TypeComp.propsType = {
objOfProp: PropTypes.arrayOf(PropTypes.number),
}

Here the type of objOfProp must be an object and the property values of the object must be a number.

We can specify the shape of our prop by using PropTypes.shape({})

An object taking on a particular shape

function TypeComp(props) {
return <div>Name: {props.shapeOfProp.name}, Age:{props.shapeOfProp.age}</div>
}
TypeCompo.defaultProps = {

}

TypeComp.propsType = {
shapeOfProp: PropTypes.shape({
name: PropTypes.string,
age: PropTypes.number
}),
}

So we must pass something like this:

<TypeComp shapeOfProp={{name:"nnamdi",age:20}}

Error will be thrown if we do:

<TypeComp shapeOfProp={{name:"nnamdi",age:"20"}}

or

<TypeComp shapeOfProp={{name:"nnamdi", rank:2}}

PropTypes: Pass anything

We can specify to accept prop of any type, just anything be it number, string, symbol etc.

PropTypes.any.isRequired

PropTypes: Required validation

We can tell React to make sure that a prop is provided. To do that, we append isRequired to any PropTypes validator.

PropTypes.string.isRequired // a prop of string must be provided.

PropTypes.func.isRequired // The prop must be a function and it must be provided else error will be thrown.

PropTypes: Custom validation

We can even specify our own prop validation function.

function TypeComp(props) {
return <div></div>
}

TypeCompo.defaultProps = {

}

TypeComp.propsType = {
customProp: function(props,propName,component) {
if(!regex.test(props[propName])){
return new Error(`Invalid prop passed to ${component}`)
}
}
}

Conclusion

We learned a lot in this post, how to check for types in our React components.

This validation will help us predict the outcome of or React components during development.

If you have any question regarding this or anything I should add, correct or remove, feel free to comment, email or DM me.Thanks !!!

Credits

--

--

JS | Blockchain dev | Author of “Understanding JavaScript” and “Array Methods in JavaScript” - https://app.gumroad.com/chidumennamdi 📕