Donald T
Donald T

Reputation: 10637

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!

Upvotes: 0

Views: 1276

Answers (2)

tobiasandersen
tobiasandersen

Reputation: 8680

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>
    )
  }
}

Upvotes: 1

azium
azium

Reputation: 20614

I think the React way of doing this would simply be:

// using a function for brevity, but could be a class
let Panel = ({ title, handleSubmit, children }) =>
  <div>
    <h1>{title}</h1>
    {children}
    <button onClick={handleSubmit}>Submit</button>
  </div>

Then elsewhere:

<Panel title="Foo" handleSubmit={onSubmit}>{specificChildContent}</Panel>

Upvotes: 2

Related Questions