In v0.14 the React team introduced stateless functional components. They are implemented by functions that take a props argument and return JSX. They’re simpler and offer performance benefits. The drawbacks are the lack of state and the inability to interact directly with the DOM.
Icon Component
In a typical app, most React components will be presentational only and should be stateless functional components. A good example is an Icon component I created to encapsulate Font Awesome icons. The component renders an <i></i>
HTML element with the proper Font Awesome classes. For example, the JSX declaration <Icon name="envelope" fixedWidth />
renders the HTML <i class="fa fa-envelope fa-fw"></i>
which displays an envelope icon with a fixed width.
The Icon source code is organized as a Common JS module. At the top, I import a few dependencies including React. The component is the Icon
function with a single props argument. In the envelope example, props would contain two properties name
and fixedWidth
with the values envelope and true respectively.
var React = require('react');
var filterKeys = require('./filterKeys');
var classnames = require('classnames');
// The component is implemented as a function.
var Icon = function (props) {
// Snip
};
The return value is a JSX snippet. JSX is React’s way of describing how the component should be rendered. It’s similar to HTML, but it needs to be translated to Javascript before it will work in the browser.
return <i {...elementProps} className={className}></i>;
After the function declaration, a property named propTypes
is added to the function. More on propTypes
in the next section.
Finally, the Icon
function is exported via module.exports
.
module.exports = Icon;
PropTypes
React has the concept of propTypes
to handle prop validation. PropTypes ensure the correct prop values are being passed to a component. If a prop is of the wrong type or a required one is missing, React will log a warning.
In the case of the Icon component, the propTypes
specification is added as a property of the component function. It is an object whose properties export validators from the React.PropTypes namespace.
Icon.propTypes = {
name: React.PropTypes.string.isRequired,
spin: React.PropTypes.bool,
fixedWidth: React.PropTypes.bool,
className: React.PropTypes.string
};
The Icon component has one required property name
and three optional properties spin
, fixedWidth
, and className
. The props name
, spin
, and fixedWidth
are translated into Font Awesome CSS classes to provide the icon type, spin animation, and a fixed width. They are composed using Classnames, a simple utility for joining CSS classes.
var className = classnames('fa', 'fa-' + props.name, props.className, {
'fa-spin': props.spin,
'fa-fw': props.fixedWidth
});
Filtering PropTypes
The available props aren’t limited to those defined in propTypes
. Consumers of Icon
can supply extra props that are applied directly to the <i></i>
element using the spread operator.
<i {...elementProps} className={className}></i>
Before they are added to the element, an additional step is needed to filter out the props specified by propTypes
. This prevents name, spin, and fixedWidth from being included in the final HTML output as attributes.
var elementProps = filterKeys(props, Icon.propTypes);
The custom filterKeys
method takes the keys defined by propTypes
and removes them from props. It returns a new object containing only the items that apply to the <i></i>
element. This approach de-clutters the rendered HTML and cuts down on the final script size.
Component Composition
Now we have a component that renders icons, but how can it be used? React components can either be rendered directly into the HTML page like below:
// Rendering the Icon directly into the page isn't very useful.
ReactDOM.render(<Icon name="envelope" />, document.getElementById('icon'))
…or they can be composed into more complex components. In the example below, a Button component displays a contextual icon on the button itself. The icon
prop indicates which image to render, and waiting
displays a spinner.
The icon is conditionally created and saved to a local variable depending on the values of the waiting
and icon
props. Notice that JSX can be saved to a variable just like any other Javascript expression.
var icon = null;
if (props.waiting) {
icon = <Icon name="spinner" fixedWidth spin style={iconStyle} />;
} else if (props.icon) {
icon = <Icon name={props.icon} fixedWidth style={iconStyle} />;
}
Next the icon is added to the JSX returned by the function. The value of the icon variable may be null, but that’s fine. React will ignore any null or undefined values.
return <button {...elementProps} type="button" onClick={onClick}>{icon}{props.children}</button>;
The snippet below creates a button with an envelope icon and sets the value of waiting
to this.state.waiting
. This state value comes from a stateful component that’s hosting the button. As the parent component’s value for this.state.waiting
changes, the button will be re-rendered with the new prop values.
var Button = require('button');
<Button className="btn btn-primary" onClick={this.send} icon="envelope" waiting={this.state.waiting}>Send Message</Button>
Wrapping Up
Icon and Button are simple and carry no internal state. That makes them ideal candidates to be implemented as stateless functional components. They can be used to build more complex components which in turn can be composed into ever more complex components. Eventually state is thrown into the mix, and you have a React application.