Search code examples
inheritancereactjssubclasscompositionsubclassing

Props in inheritance and composition


I have a React class named Panel that I would like to serve as a reusable component for various kinds of specific panels in a UI. Each panel has in common a title bar and a "Submit" button, but the body of each kind of panel is unique.

I could use either inheritance (sub-classing) or composition to achieve this, but which would be best in this case?

I have tried sub-classing, having a render method in the parent Panel and then having child panels override a renderBody method, which render uses. That seems to break down because each specific panel needs its own props (such as a "title"), and React complains when modified props are passed to super in a component constructor (the error message is, "When calling super() . . . make sure to pass up the same props that your component's constructor was passed."). Since the "title" is specific to a kind of panel, I don't want the end consumer having to specify a "title" prop itself.

class Panel extends React.Component {

  render() {
    return (
      <div>
        <div>{this.props.title}</div>
        <div>{this.renderBody()}</div>
        <div><button>Submit</button></div>
      </div>
    )
  }

}

class SomeSubPanel extends Panel {

  constructor(props) {
    // React throws the error message at the following line
    let newProps = Object.assign({}, props, {title: "Some Sub Panel"})
    super(newProps)
  }

  renderBody() {
    return (<div>Panel Body Goes Here</div>)
  }

}

Using composition wouldn't seem to be as tidy as sub-classing because each panel only needs to have a specific body, yet the body (which is HTML) can't be passed between components.

What would be the "React way" of having child components that can pass traits to a reusable parent component?

Many thanks!


Solution

  • Definitely use composition. Generally, I don't think you should ever extend your own React components. Here's how you could achieve it:

    class ReusablePanel extends React.Component {
      render () {
        return (
          <div>
            <div>{this.props.title}</div>
            <button onClick={this.props.onSubmit}>Submit</button>
            {this.props.children}
          </div>
        )
      }
    }
    
    class FootballPanel extends React.Component {
      handleSubmitButtonClick = () => {
        // do something
      }
    
      render () {
        return (
          <ReusablePanel title='Football' onSubmit={this.handleSubmitButtonClick}>
            <div>{/* Football markup */}</div>
          </ReusablePanel>
        )
      }
    }
    
    class ArsenalPanel extends React.Component {
      handleSubmitButtonClick = () => {
        // do something
      }
    
      render () {
        return (
          <ReusablePanel title='Arsenal' onSubmit={this.handleSubmitButtonClick}>
            <div>{/* Arsenal markup */}</div>
          </ReusablePanel>
        )
      }
    }