Elegant React Component APIs with Functions as Children
We can develop some pretty elegant React Component APIs using functions as children in React Components. Take for example a Dropdown component. If we want it to be flexible by leaving the DOM structure up to the user, we would need some way to designate toggler elements of the Dropdown. One way is with the data-toggle
attribute:
However, this will require manually setting up event listeners on real DOM nodes, for example, the
componentDidMount
method on this component might look something like this:
componentDidMount() { const togglers = ReactDOM .findDOMNode(this) .querySelectorAll('[data-toggle]'); Array.prototype.forEach.call(togglers, toggler => { toggler.addEventListener('click', this.toggle); }); },
A more elegant solution is to expose the component’s toggle
method to its children by using a function as a child:
{toggle =>
}
This way, we’re using React’s event system instead of raw DOM events, and we wouldn’t need to implement componentDidMount
at all.
When toggle
is called, the opened
CSS class will be toggled on the div
element. In other words, the component would generate DOM that looks like this:
and when the toggle
function is called, the opened
class is added to the element:
The implementation of this Dropdown component looks like this:
const cx = require('classnames'); const enhanceWithClickOutside = require('react-click-outside'); const React = require('react'); const Dropdown = React.createClass({ getInitialState() { return { opened: false, }; }, handleClick(e) { // Close dropdown when clicked on a menu item if (this.state.opened && e.target.tagName === 'A') { this.setState({ opened: false }); } }, handleClickOutside(e) { if (!this.state.opened) return; this.setState({ opened: false }); }, toggle() { this.setState({ opened: !this.state.opened }); }, render() { const child = this.props.children(this.toggle); return React.cloneElement(child, { className: cx( child.props.className, 'dropdown', this.state.opened && 'opened' ), onClick: this.handleClick, }); }, }); module.exports = enhanceWithClickOutside(Dropdown);
this.props.children
is the function child of the Dropdown component, and it is called with the instance’s toggle
method. This returns a React Element, the div
, which we clone to add the dropdown
and opened
css classes.
A discussion of this pattern and other real world use cases can be found here.