We're looking at how to make reusable React components today so we can share our components across apps and teams.
Phew! We made it to week two (relatively unscathed)! Through this point, we've talked through most of the basic features of React (props
, state
, life-cycle hooks, JSX, etc.).
In this section, we're going to look a bit at annotating and packaging our components.
PropTypes
You may have noticed we use props
quite a bit in our components. For the most part, we'll expect these to be a particular type or set of types (aka an object
or a string
). React provides a method for defining and validating these types that allow us to easily expose a component API.
Not only is this a good practice for documentation purposes, it's great for building reusable react components.
The prop-types
object exports a bunch of different types which we can use to define what type a component's prop should be. We can define these using the propTypes
method in the ES6 class-style React prop:
class Clock extends React.Component {
// ...
}
Clock.propTypes = {
// key is the name of the prop and
// value is the PropType
}
From within this prop
, we can define an object which has the key of a prop as the name of the prop we are defining and a value defines the type (or types) it should be defined as.
For instance, the Header
component we built a few days ago accepts a a prop called title
and we expect it to be a string. We can define it's type to be a string as such:
First, we'll need to
import
thePropTypes
object from theprop-types
package using theimport
keyword again:import PropTypes from 'prop-types'
You can also use the
PropTypes
object directly in your browser by adding the followingscript
tag in your page<script src="https://unpkg.com/[email protected]/prop-types.min.js"></script>
import PropTypes from 'prop-types'
class Header extends React.Component {
// ...
}
Header.propTypes = {
title: PropTypes.string
}
React has a lot of types to choose from, exported on the PropTypes
object and even allows for us to define a custom object type. Let's look at an overall list of available types:
Basic types
React exposes a few basic types we can use out of the box.
type | example | class |
---|---|---|
String | 'hello' | PropTypes.string |
Number | 10, 0.1 | PropTypes.number |
Boolean | true / false | PropTypes.bool |
Function | const say => (msg) => console.log("Hello world") |
PropTypes.func |
Symbol | Symbol("msg") | PropTypes.symbol |
Object | {name: 'Ari'} |
PropTypes.object |
Anything | 'whatever', 10,
{} |
It's possible to tell React we want it to pass through anything that can be rendered by using PropTypes.node
:
type | example | class |
---|---|---|
A rendererable | 10, 'hello' | PropTypes.node |
Clock.propTypes = {
title: PropTypes.string,
count: PropTypes.number,
isOn: PropTypes.bool,
onDisplay: PropTypes.func,
symbol: PropTypes.symbol,
user: PropTypes.object,
name: PropTypes.node
}
We've already looked at how to communicate from a parent component to a child component using props
. We can communicate from a child component to a parent component using a function. We'll use this pattern quite often when we want to manipulate a parent component from a child.
Collection types
We can pass through iterable collections in our props
. We've already seen how we can do this when we passed through an array with our activities. To declare a component's proptype as an array, we can use the PropTypes.array
annotation.
We can also require that an array holds only objects of a certain type using PropTypes.arrayOf([])
.
type | example | class |
---|---|---|
Array | [] | PropTypes.array |
Array of numbers | [1, 2, 3] | PropTypes.arrayOf([type]) |
Enum | ['Red', 'Blue'] | PropTypes.oneOf([arr]) |
It's possible to describe an object that can be one of a few different types as well using PropTypes.oneOfType([types])
.
Clock.propTypes = {
counts: PropTypes.array,
users: PropTypes.arrayOf(PropTypes.object),
alarmColor: PropTypes.oneOf(['red', 'blue']),
description: PropTypes.oneOfType([
PropTypes.string,
PropTypes.instanceOf(Title)
]),
}
Object types
It's possible to define types that need to be of a certain shape or instance of a certain class.
type | example | class |
---|---|---|
Object | {name: 'Ari'} |
PropTypes.object |
Number object | {count: 42} |
PropTypes.objectOf() |
Instance | new Message() |
PropTypes.objectOf() |
Object shape | {name: 'Ari'} |
PropTypes.shape() |
Clock.propTypes = {
basicObject: PropTypes.object,
numbers: PropTypes
.objectOf(PropTypes.numbers),
messages: PropTypes
.instanceOf(Message),
contactList: PropTypes.shape({
name: PropTypes.string,
phone: PropTypes.string,
})
}
React types
We can also pass through React elements from a parent to a child. This is incredibly useful for building templates and providing customization with the templates.
type | example | class |
---|---|---|
Element | <Title /> |
PropTypes.element |
Clock.propTypes = {
displayEle: PropTypes.element
}
When we use element, React expects that we'll be able to accept a single child component. That is, we won't be able to pass multiple elements.
// Invalid for elements
<Clock displayElement={
<div>Name</div>
<div>Age</div>
}></Clock>
// Valid
<Clock displayElement={
<div>
<div>Name</div>
<div>Age</div>
</div>
}></Clock>
Requiring types
It's possible to require a prop to be passed to a component by appending any of the proptype descriptions with .isRequired
:
Clock.propTypes = {
title: PropTypes.name.isRequired,
}
Setting a prop
as required is very useful for times when the component is dependent upon a prop
to be passed in by it's parent component and won't work without it.
Custom types
Finally, it's also possible to pass a function to define custom types. We can do this for a single prop or to validate arrays. The one requirement for the custom function is that if the validation does not pass, it expects we'll return an Error
object:
type | example | class |
---|---|---|
Custom | 'something_crazy' | function(props, propName, componentName) {} |
CustomArray | ['something', 'crazy'] | PropTypes.arrayOf(function(props, propName, componentName) {}) |
UserLink.propTypes = {
userWithName: (props, propName, componentName) => {
if (!props[propName] || !props[propName].name) {
return new Error(
"Invalid " + propName + ": No name property defined for component " + componentName
)
}
}
}
Default props
Sometimes we want to be able to set a default value for a prop. For instance, our <Header />
component, we built yesterday might not require a title to be passed. If it's not, we'll still want a title to be rendered, so we can define a common title instead by setting it's default prop value.
To set a default prop value, we can use the defaultProps
object key on the component.
Header.defaultProps = {
title: 'Github activity'
}
Phew, today we went through a lot of documentation. It's always a good idea to build our resuable components using the propTypes
and defaultProps
attributes of components. Not only will it make it easier to communicate across developers, it'll be much easier when we return to our components after leaving them for a few days.
Next, we'll get back to code and start integrating some style into our components.