React at Preact

Pages

While Pages are technically Smart Components themselves, we make a distinction between the two concepts in the context of a React app to ensure simple structure and organization.

Pages

Pages are generally addressable via a URL and may read one or more params from the url or querystring. Pages are roughly similar to actions in a MVC controller application and you may have pages like PersonShow or PersonIndex in an application.

It is the responsibility of the Page to bind to changes in the Flux Stores which are relevant to the page or its children. Pages will also be generally responsible for calling Flux Actions to make changes to data when children components request it.

Parent-child relationships

A common pattern is to pass a function on the Page into a component as a prop to enable the child component to make the callback when an action is taken in the interface. For clean separability, it is important that parent components (or pages, in this case) are unaware of the DOM structure generated by any children. Likewise, children components should not have knowledge of where their data comes from or how to persist it to a server, etc.

For Example

Consider the following example of a page and component:

export default class SomePage extends React.Component {

  onItemNameChanged (id, name) {
    this.getFlux().actions.itemActions.updateItemName({ id: id, name: name });
  }

  render () {
    return (
      <ul>
        { this.state.items.map( item =>
          <ItemInlineEditor item={ item } key={ item.id } onNameChanged={ this.onItemNameChanged } />
        ) }
      </ul>
    );
  }

};

export default class ItemInlineEditor extends React.Component {

  saveNow () {
    var itemId = this.props.item.id;
    var newName = React.findDOMNode(this.refs.itemName).value;
    this.props.onNameChanged(itemId, newName);
  }

  render () {
    return (
      <li>
        <input ref='itemName' value={ this.props.item.name } onBlur={ this.saveNow } />
      </li>
    );
  }

}

Above, the ItemInlineEditor component is responsible for rendering the passed in item binding to the appropriate onBlur event internally on the component, finding the right DOM element from which to extract the new value, and then calling this.props.onNameChanged with the id and new name value.

This component is very reusable and could be used anywhere in the application regardless of what item is passed in.

The SomePage page component has some state.items array from which it renders multiple ItemInlineEditor components, passing in the individual item. It also passes down the page function onItemNameChanged to handle exactly how to react to changes in the item name.

This is how we maintain complete logical separation of responsibilities between Pages and our reusable Components.

Example 2

If we wanted to change the above SomePage page to use a different type of item editor component, all we would need to do is create the new component using a button to save instead of the inline editor:

export default class ItemButtonEditor extends React.Component {

  onSaveClick () {
    var itemId = this.props.item.id;
    var newName = React.findDOMNode(this.refs.itemName).value;
    this.props.onNameChanged(itemId, newName);
  }

  render () {
    return (
      <li>
        <input ref='itemName' value={ this.props.item.name } />
        <button value='Save' onClick={ this.onSaveClick } />
      </li>
    );
  }

}

And then update the render method in SomePage to use our new component.

<ItemButtonEditor item={ item } key={ item.id } onNameChanged={ this.onItemNameChanged } />

Because the other methods in SomePage were never aware of the implementation details in the child component, we are all done.