React Compound Components

October 26, 2018


There are situations when we need to pass props to multiple children components. Manually passing props makes app tightly coupled and less flexible. One solution is to use compound components.

Think of compound components like the <select> and <option>elements in HTML. Apart they don’t do too much, but together they allow you to create the complete experience. — Kent C. Dodds

import React from "react";
import { render } from "react-dom";
import { Radio } from "antd";

import "./index.css";
import "antd/dist/antd.css";
const bananas = require("./bananas.json");

import List from "./List";
import Item from "./Item";
const RadioButton = Radio.Button;
const RadioGroup = Radio.Group;

export default class App extends React.Component {
  state = {
    items: bananas.bananas,
    size: "normal"
  };

  onChange = e => {
    this.setState({ size: e.target.value });
  };

  selectItem = title => () => this.setState({ selectedItem: title });
  
  render() {
    const { items, size } = this.state;
    return (
      <div>
        <RadioGroup onChange={this.onChange} defaultValue="normal">
          <RadioButton value="small">small</RadioButton>
          <RadioButton value="normal">normal</RadioButton>
          <RadioButton value="large">large</RadioButton>
        </RadioGroup>
        <List
          size={size}
          selectItem={this.selectItem}
        >
          {items.map(
            ({ id, title }) => <Item key={id} title={title} />
          )}
        </List>
        {selectedItem}
      </div>
    );
  }
}

render(<App />, document.getElementById("root"));
import React from "react";

const List = ({ size, children, selectItem }) => {
  const listChildren = React.Children.map(children, child => {
    return React.cloneElement(child, { size, selectItem });
  });
  return <ul>{listChildren}</ul>;
};

export default List;
import React from "react";

const styles = {
  small: {
    fontSize: "12px"
  },
  medium: {
    fontSize: "14px"
  },
  large: {
    fontSize: "16px"
  }
};

const Item = ({ title, size, selectItem }) => (
  <li
    style={styles[size]}
    onClick={selectItem(title)}>
    {title}
  </li>
)

export default Item;

Codesanbox example here.