Search code examples
javascriptreactjsfluxreactjs-flux

Abstraction in React.js


I want to use some abstraction in the creation of my React components. For example:

class AbstractButton extends React.Component {
  render() {
    return (
      <button
        onClick={this.props.onClick}
        className={this.definitions.className}>
        {this.props.text}
      </button>
    }
}
class PrimaryButton extends AbstractButton {
  constructor(options) {
    super(options);
    this.definitions = {
        className: 'btn btn-primary'
    };
  }
}
class SuccessButton extends AbstractButton {
  constructor(options) {
    super(options);
    this.definitions = {
        className: 'btn btn-success'
    };
  }
}

I don't want to pass these definitions via props because I know that these definitions--in this case the class--will never change.

Is it an anti-pattern in React? Or is it OK?

My question refers to this altjs issue: this kind of abstraction isn't compatible with @connectToStores.


Solution

  • Generally speaking, there's no reason not to use composition here instead of deep inheritance:

    class Button extends React.Component {
      render() {
        return (<button
                 onClick={this.props.onClick}
                 className={this.props.className}
               >
                 {this.props.text}
               </button>);
      }
      static propTypes = {
          className: React.PropTypes.string.isRequired,
          onClick: React.PropTypes.func
      }
    }
    
    class PrimaryButton extends React.Component {
      render() {
        return <Button {...this.props} className="btn btn-primary" />;
      }
    }
    

    This is just as functional as what you propose, but is a lot simpler and easier to reason about. It makes it very clear what information your Button actually needs to do its work.

    Once you make this leap, you can eliminate the classes altogether and use stateless components:

    const Button = (props) => (<button
                 onClick={props.onClick}
                 className={props.className}
               >
                 {props.text}
               </button>);
    Button.propTypes = {
          className: React.PropTypes.string.isRequired,
          onClick: React.PropTypes.func
    };
    
    const PrimaryButton = (props) =>
        <Button {...props} className="btn btn-primary" />;
    
    const SuccessButton = (props) =>
        <Button {...props} className="btn btn-success" />;
    

    This will allow React to apply more optimizations since your components do not need any special lifecycle event support or state management. It is also even easier to reason about since now you are just working with pure functions.

    As an aside, if you are trying to make some React components that wrap Bootstrap, then perhaps you should take a look at React-Bootstrap.