|

Elegant React Component APIs with Functions as Children

react
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:

Open
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.

Similar Posts