My book on iOS interface design, Design Teardowns: Step-by-step iOS interface design walkthroughs is now available!

Building generic components in React

Communication between components in React is typically in one direction, i.e. from parent components to child components. We largely use props to pass information like this:

<MyVeryAwesomeComponent title="This is simply amazing!" />  

We can use props to solve the problem of building more general components by abstracting away more specific markup. Say we have a list of items - we can hide the implementation details by building a child component like this:

export default React.createClass({  
  render() {
    let items = this.props.items || []
    let rows = items.map(
      item => {
        return (
          <tr>
            <td>{item.title}</td>
          </tr>
        );
      }
    );

    return (
      <table>
        {rows}
      </table>
    );
  }
});

This way we can use this component like this:

<List items={items} />  

Now let's say we want to have 2 similar components like follows:

What do we do? We could duplicate this component and make the change in the one column, but that would violate DRY.

Instead, we can pass in the customizable portion in via props:

export default React.createClass({  
  render() {
    let items = this.props.items || []    
    let customComponent = this.props.customComponent || <span />

    let rows = items.map(
      item => {
        return (
          <tr>
            <td>{item.title}</td>
            <td>{customComponent}</td>
          </tr>
        );
      }
    );

    return (
      <table>
        {rows}
      </table>
    );
  }
});

Now we can create components like this:

<List items={items} customComponent={  
  <button>ADD</button>
} />

<List items={items} customComponent={  
  <div>
    <button>APPROVE</button><br />
    <button>REJECT</button>
  </div>
} />

This is an improvement over plain copy and paste, but now we've got a different problem. We can add an event handler (like onClick) to our customComponent but we've lost a the reference to the item in the table row.

To solve this, we need a way to communicate back from our child component to our parent component, since we map over items within the child.

We can do that by turning our prop into a callback like this:

export default React.createClass({  
  render() {
    let items = this.props.items || [];
    let customComponentGenerator = this.props.customComponentGenerator || function(){ return <span /> };

    let rows = items.map(
      item => {
        return (
          <tr key={item.title}>
            <td>{item.title}</td>
            <td>{customComponentGenerator(item)}</td>
          </tr>
        );
      }
    );

    return (
      <table>
        {rows}
      </table>
    );
  }
});

Now we can adjust the parent like this:

<List items={items} customComponentGenerator={  
  (item) => {
    return (
      <button onClick={
        ()=>console.log('add '+item.title)}>
        ADD
      </button>
      );
  }
} />

<List items={items} customComponentGenerator={  
  (item) => {
    return (
      <div>
        <button onClick={
         ()=>console.log('approve '+item.title)}>
         APPROVE
        </button><br />
        <button onClick={
          ()=>console.log('reject '+item.title)}>
          REJECT
        </button>
      </div>
      );
  }
} />

And that's a couple of ways we can build more generic components in React!