Type and value validators

In this section, we'll look at the more advanced validator functionality available in the React PropTypes facility. First, you'll learn about the element and node validators that check for values that can be rendered inside HTML markup. Then, you'll see how to check for specific types, beyond the primitive type checking you saw in the previous section. Finally, we'll implement validation that looks for specific values.

Things that can be rendered

Sometimes, you just want to make sure that a property value is something that can be rendered by JSX markup. For example, if a property value is an array, this can't be rendered by putting it in {}. You have to map the array items to JSX elements.

This sort of checking is especially useful if your component passes property values to other elements as children. Let's look at an example of what this looks like:

import React, { PropTypes } from 'react'; 
 
const MyComponent = ({ 
  myHeader, 
  myContent, 
}) => ( 
  <section> 
    <header>{myHeader}</header> 
    <main>{myContent}</main> 
  </section> 
); 
 
// The "myHeader" property requires a React 
// element. The "myContent" property requires 
// a node that can be rendered. This includes 
// React elements, but also strings. 
MyComponent.propTypes = { 
  myHeader: PropTypes.element.isRequired, 
  myContent: PropTypes.node.isRequired, 
}; 
 
export default MyComponent; 

This component has two properties that require values that can be rendered. The myHeader property wants an element. This can be any JSX element. The myContent property wants a node. This can be any JSX element or any string value. Let's pass this component some values and render it:

import React from 'react'; 
import { render } from 'react-dom'; 
 
import MyComponent from './MyComponent'; 
 
// Two React elements we'll use to pass to 
// "<MyComponent>" as property values. 
const myHeader = (<h1>My Header</h1>); 
const myContent = (<p>My Content</p>); 
 
render(( 
  <section> 
    { /* Renders as expected, both properties are passed 
         React elements as values. */ } 
    <MyComponent 
      {...{ myHeader }} 
      {...{ myContent }} 
    /> 
 
    { /* Triggers a warning because "myHeader" is expecting 
         a React element instead of a string. */ } 
    <MyComponent 
      myHeader="My Header" 
      {...{ myContent }} 
    /> 
 
    { /* Renders as expected. A string is a valid type for 
         the "myContent" property. */ } 
    <MyComponent 
      {...{ myHeader }} 
      myContent="My Content" 
    /> 
 
    { /* Renders as expected. An array of React elements 
         is a valid type for the "myContent" property. */ } 
    <MyComponent 
      {...{ myHeader }} 
      myContent={[myContent, myContent, myContent]} 
    /> 
  </section> 
  ), 
  document.getElementById('app') 
); 

As you can see, the myHeader property is more restrictive about the values it will accept. The myContent property will accept a string, an element, or an array of elements. These two validators are important when passing in child data from properties, as this component does. For example, trying to pass a plain object or a function as a child will not work, and it's best if you check for this situation using a validator.

Here's what this component looks like when rendered:

Things that can be rendered

Requiring specific types

Sometimes, you need a property validator that checks for a type defined by your application. For example, let's say you have the following user class:

import cuid from 'cuid'; 
 
// Simple class the exposes an API that the 
// React component expects. 
export default class MyUser { 
  constructor(first, last) { 
    this.id = cuid(); 
    this.first = first; 
    this.last = last; 
  } 
 
  get name() { 
    return `${this.first} ${this.last}`; 
  } 
} 

Now, suppose that you have a component that wants to use an instance of this class as a property value. You would need a validator that checks that the property value is an instance of MyUser. Let's implement a component that does just that:

import React, { PropTypes } from 'react'; 
import MyUser from './MyUser'; 
 
const MyComponent = ({ 
  myDate, 
  myCount, 
  myUsers, 
}) => ( 
  <section> 
    { /* Requires a specific "Date" method. */ } 
    <p>{myDate.toLocaleString()}</p> 
 
    { /* Number or string works here. */ } 
    <p>{myCount}</p> 
    <ul> 
      { /* "myUsers" is expected to be an array of 
           "MyUser" instances. So we know that it's 
           safe to use the "id" and "name" property. */ } 
      {myUsers.map(i => ( 
        <li key={i.id}>{i.name}</li> 
      ))} 
    </ul> 
  </section> 
); 
 
// The properties spec is looking for an instance of 
// "Date", a choice between a string or a number, and 
// an array filled with specific types. 
MyComponent.propTypes = { 
  myDate: PropTypes.instanceOf(Date), 
  myCount: PropTypes.oneOfType([ 
    PropTypes.string, 
    PropTypes.number, 
  ]), 
  myUsers: PropTypes.arrayOf(PropTypes.instanceOf(MyUser)), 
}; 
 
export default MyComponent; 

This component has three properties that require specific types each that go beyond the basic type validators that you've seen so far in this chapter. Let's walk through these now:

  • myDate requires an instance of Date. It uses the instanceOf() function to build a validator function that ensures the value is in fact a Date instance.
  • myCount requires that the value either by a number or a string. This validator function is created by combining oneOfType, PropTypes.number(), and PropTypes.string().
  • myUsers requires an array of MyUser instances. This validator is built by combining arrayOf() and instanceOf().

This example illustrates the number of scenarios that we can handle by combining the property validators provided by React. Here's what the rendered output looks like:

Requiring specific types

Requiring specific values

We've focused on validating the type of property values so far, but that's not always what we'll want to check for. Sometimes, specific values matter. Let's see how we can validate for specific property values:

import React, { PropTypes } from 'react'; 
 
// Any one of these is a valid "level" 
// property value. 
const levels = new Array(10) 
  .fill(null) 
  .map((v, i) => i + 1); 
 
// This is the "shape" of the object we expect 
// to find in the "user" property value. 
const userShape = { 
  name: PropTypes.string, 
  age: PropTypes.number, 
}; 
 
const MyComponent = ({ 
  level, 
  user, 
}) => ( 
  <section> 
    <p>{level}</p> 
    <p>{user.name}</p> 
    <p>{user.age}</p> 
  </section> 
); 
 
// The property spec for this component uses 
// "oneOf()" and "shape()" to define the required 
// property vlues. 
MyComponent.propTypes = { 
  level: PropTypes.oneOf(levels), 
  user: PropTypes.shape(userShape), 
}; 
 
export default MyComponent; 

The level property is expected to be a number from the levels array. This is easy to validate using the oneOf() function. The user property is expecting a specific shape. A shape is the expected properties and types of an object. The userShape defined in this example requires a name string and an age number. The key difference between shape() and instanceOf() is that we don't necessarily care about the type. We might only care about the values that are used in the JSX of the component.

Let's take a look at how this component is used:

import React from 'react'; 
import { render } from 'react-dom'; 
 
import MyComponent from './MyComponent'; 
 
render(( 
  <section> 
    { /* Works as expected. */ } 
    <MyComponent 
      level={10} 
      user={{ name: 'Name', age: 32 }} 
    /> 
 
    { /* Works as expected, the "online" 
         property is ignored. */ } 
    <MyComponent 
      user={{ name: 'Name', age: 32, online: false }} 
    /> 
 
    { /* Fails. The "level" value is out of range, 
         and the "age" property is expecting a 
         number, not a string. */ } 
    <MyComponent 
      level={11} 
      user={{ name: 'Name', age: '32' }} 
    /> 
  </section> 
  ), 
  document.getElementById('app') 
); 

Here's what the component looks like when it's rendered:

Requiring specific values

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset