The components that we've seen so far are completely static in that they take no external input and always render exactly the same. This isn't especially interesting because the same outcome can be achieved by writing plain old HTML. However, React provides a mechanism for making components dynamic by using properties, or props.
Props are passed into a component in order to modify their base definition. Let's take another look at our Title
component:
import React, { Component } from 'react';
export default class Title extends Component {
render() {
return (
<h1>
Hello World!
</h1>
);
}
}
While the title of a single article might be Hello World!
, this component needs to be more dynamic if it is to be reused within all of our NewsItem
components. For this, we'll use a React input property, or prop, called titleText
. React component methods have a this
context that gives access to properties that have been passed in:
import React, { Component } from 'react';
export default class Title extends Component {
render() {
return (
<h1>
{this.props.titleText}
</h1>
);
}
}
Once again, remember that curly brackets in JSX denotes JavaScript code. Here, we are accessing the component's titleText
prop in order to render it within the component's markup:
<h1>
{this.props.titleText}
</h1>
This by itself is sufficient code to start accepting a titleText
property. However, as a best practice, we should include in our component's definition a description of what properties it is equipped to accept. While this may seem like over-engineering and unnecessary in small projects maintained by a single developer, as the project and team grows, explicit definition of properties is key in an untyped language such as JavaScript.
Defining PropTypes
in a component is how we formally tell other developers what properties a component accepts and what value types those properties should be. PropTypes
are the same across instances of a component and are thus statically attached to the class:
import React, { Component, PropTypes } from 'react';
export default class Title extends Component {
render() {
return (
<h1>
{this.props.titleText}
</h1>
);
}
}
Title.propTypes = {
titleText: PropTypes.string
};
Adding PropTypes
to a component does not change anything functionally, but it will cause annoying warning messages to be logged to the JavaScript console when they are disobeyed (only when React is in development mode, mind you).
To use PropTypes
, we'll need to add it to the React import:
import React, { Component, PropTypes } from 'react';
The PropTypes
module comes with functions for validating different value types, such as string
, number
, and func
.
Here, what we are communicating is that this component takes one optional property called titleText
, and that property should be of type string
:
Title.propTypes = {
titleText: PropTypes.string
};
We could also make this a required property:
Title.propTypes: {
titleText: PropTypes.string.isRequired
}
In addition to having string type props, we can also have other simple types, such as booleans and numbers:
Title.propTypes = {
titleText: PropTypes.string.isRequired,
highlighted: PropTypes.bool,
fontSize: PropTypes.number
};
Props can not only be used to define the text content, but can also be used to define attributes of an element, for instance, inline style:
import React, { Component, PropTypes } from 'react';
export default class Title extends Component {
render() {
return (
<h1
style={{
backgroundColor: this.props.highlighted ? 'yellow' : 'white', fontSize: `${this.props.fontSize}px`
}}
>
{this.props.titleText}
</h1>
);
}
}
Title.propTypes = {
titleText: PropTypes.string.isRequired,
highlighted: PropTypes.bool, fontSize: PropTypes.number
};
One thing to note with the preceding example is that CSS properties that have a dash in them when written in traditional CSS use camel case in React inline style. This is because keys in JavaScript objects cannot contain dashes.
React PropType specifications can also be used to validate more complex properties. For instance, we could have a property that is either a string or a number using the oneOfType
function, which is as follows:
fontSize: PropTypes.oneOfType([
PropTypes.string,
PropTypes.number
])
Likewise, we can specify a set of specific values that a property is allowed to take by using the oneOf
method:
size: PropTypes.oneOf([
'small',
'medium',
'large'
])
We can of course specify more complex data types, such as arrays and objects, but we can also be more specific and describe the types of values in an array property or the shape that an object property takes:
propTypes: {
//Array that can contain anything
simpleArray: PropTypes.array,
//Object that can contain anything
simpleObject: PropTypes.object,
//Array that contains only Number values
arrayOfNumbers: PropTypes.arrayOf(PropTypes.number),
//Object that takes a specific "shape"
complexObject: PropTypes.shape({
id: PropTypes.number,
name: PropTypes.string
})
}
Now our Title
component is getting interesting. It has gone from something that can be easily recreated using just HTML to something more like a HTML template-still declaratively defined, but dynamic in that it can take external properties.
Alternatively, PropTypes
can be added to a React component as a static property using the static
keyword:
import React, { Component, PropTypes } from 'react';
export default class Title extends Component {
static propTypes = {
titleText: PropTypes.string.isRequired,
highlighted: PropTypes.bool,
fontSize: PropTypes.number
}
render() {
return (
<h1
style={{
backgroundColor: this.props.highlighted ? 'yellow' : 'white', fontSize: `${this.props.fontSize}px`
}}
>
{this.props.titleText}
</h1>
);
}
}
This syntax is cleaner, but is not officially part of the ECMAScript specification at this point. While most transpiler programs will recognize this syntax, we'll avoid it in this book for that reason.
With a component defined that accepts props, the next step is for props to be passed into this component. In the case of our Title
component, the NewsItem
component can pass properties into the contained Title
component. It does this using the attribute syntax of XML:
import React, { Component } from 'react';
import Title from './Title';
export default class NewsItem extends Component {
render() {
return (
<div className="news-item">
<Image />
<Title
titleText="Hello World!"
highlighted={true}
fontSize={18}
/>
<Byline />
<Description />
</div>
);
}
}
Strings are the only value types that can be passed in as a prop directly:
titleText="Hello World!"
For other JavaScript data types, such as numbers, Booleans, and arrays, we must surround the values in curly braces so that they are interpreted correctly as JavaScript:
fontSize={18}
For Boolean props, we can shorten their input to where the property name's presence is interpreted as true and its absence is interpreted as false, much like in HTML:
<div className="news-item">
<Image />
<Title
titleText="Hello World!"
highlighted
fontSize={18}
/>
<Byline />
<Description />
</div>
In a previous section, we specified, using PropTypes
, that the titleText
property of the Title
component is required, but the other two properties are optional. This raises an interesting question: what will the value of those properties be if they are not specified? Well, without any intervention from the component developer, those properties will appropriately have the value undefined
when no value is passed in. This could be problematic in some situations.
For our fontSize
property, a value of undefined
could lead to some unpredictable and potentially error-prone code because it is expecting a number. Luckily for us, React has a mechanism for specifying default values for optional properties that have not been passed in explicitly. This mechanism is a method on the component called defaultProps
and we can use it in Title
, statically, like this:
import React, { Component, PropTypes } from 'react';
export default class Title extends Component {
render() {
return (
<h1
style={{
backgroundColor: this.props.highlighted ? 'yellow' : 'white',
fontSize: `${this.props.fontSize}px`
}}
>
{this.props.titleText}
</h1>
);
}
}
Title.propTypes = {
titleText: PropTypes.string.isRequired,
highlighted: PropTypes.bool,
fontSize: PropTypes.number
};
Title.defaultProps = {
highlighted: false,
fontSize: 18
};
defaultProps
must be a JavaScript object where keys are property names and the values are the default values to use in the case that no values were passed in for that particular property. We can now define a Title
component that isn't highlighted and has the default font size of 18 pixels by simply writing the following:
<Title
titleText="Hello World!"
/>
In context, our NewsItem
component is now simplified to this:
import React, { Component } from 'react';
import Title from './Title';
export default class NewsItem extends Component {
render() {
return (
<div className="news-item">
<Image />
<Title
titleText="Hello World!"
highlighted
/>
<Byline />
<Description />
</div>
);
}
}
Sometimes, a component will receive its props from several levels above. For instance, maybe NewsFeed
specifies the title of an individual NewsItem
, rather than having NewsItem
provide it statically itself, as we have done in the previous examples. Parameterizing this property allows the NewsItem
component to be more generic and reusable:
import React, { Component, PropTypes } from 'react';
import Title from './Title';
export default class NewsItem extends Component {
render() {
return (
<div className="news-item">
<Image />
<Title
titleText={this.props.titleText}
highlighted
/>
<Byline />
<Description />
</div>
);
}
}
NewsItem.propTypes = {
titleText: PropTypes.string.isRequired
};
Here, we have shown how the NewsItem
component can accept a property, and in turn, pass it down to the Title
component.
Every component has an optional special property that is called children. Normal properties, as we have seen, are passed in using something similar to the HTML attribute syntax:
<Title
titleText="Hello World"
/>
You can also pass in text or other component elements by placing them in between an opening and closing tag. We can refactor our Title
component to accept children instead of the titleText
prop:
<Title>
Hello World
</Title>
Now, the render()
method of our Title
component becomes this:
render() {
return (
<h1
style={{
backgroundColor: this.props.highlighted ? 'yellow' : 'white', fontSize: `${this.props.fontSize}px`
}}
>
{this.props.children}
</h1>
);
}
Note that we could now also pass in other React elements into the Title
as property by also placing them in between the opening and closing tags:
<Title>
Hello World!
<img src="icon.png" />
</Title>
When validating the children
prop, we can use a special PropTypes
called node
, which means anything that can be rendered by React:
Title.propTypes = {
children: PropTypes.node.isRequired,
highlighted: PropTypes.bool,
fontSize: PropTypes.number
};