Niekert
Niekert

Reputation: 947

Require children of certain type in React Component

I am creating a custom React Component for a Dropdown menu. The structure I had in mind to use this component was something among the lines of

<Dropdown>
  <DropdownTrigger> // The button that triggers the dropdown to open or close
    open me
  </DropdownTrigger>
  <DropdownButton> // A button inside the dropdown
    Option 1
  </DropdownButton>
</Dropdown>

The way I wanted to implement this, is by having the Dropdown component check if there is a DropdownTrigger component in its children, and if there is, clone the given DropdownTrigger component using React.cloneElement and pass an onClick property, that calls a state update on the Dropdown component.

Small code snippet of Dropdown.js

  import DropdownTrigger from './DropdownTrigger';

  _toggleDropdown () {
    this.setState({
      isOpen: !this.state.isOpen
    });
  }


  _renderTriggerButton () {
    const triggerChild = this.props.children.find(child => child.type === DropdownTrigger);
    return React.cloneElement(triggerChild, {
      ...triggerChild.props,
      onClick: () => this._toggleDropdown()
    });
  }

Is this a correct approach and if so, what would be the cleanest/nicest possible way to validate the Dropdown component has a DropdownTrigger as child. As this means a developer always has to include a DropdownTrigger as child of the Dropdown component, I would like to have a nice way to tell developer they should pass a <TriggerButton> component as child of the dropdown.

I'm also open for suggestions about changes in the structure. Thanks!

Upvotes: 4

Views: 1455

Answers (1)

Lyubomir
Lyubomir

Reputation: 20027

Use React.cloneElement to pass additional properties to child components. In combination with instanceof you can do exactly what you want.


It could be something along the lines...

import React from 'react';
import DropdownTrigger from './DropdownTrigger';

class DropDown extends React.Component {
  constructor(props) {
    super(props);
  }

  ...

  render() {
    return (
      <div>
        {React.Children.map(this.props.children, child => {
          const additionalProps = {}
          // add additional props if it's the DropdownTrigger
          if (child instanceof DropdownTrigger) {
            additionalProps.toggleDropDown = () => { } ...
          }

          return React.cloneElement(child, additionalProps);
        })}
      </div>
    );
  }
}

export default DropDown;

Upvotes: 4

Related Questions