Reputation: 10637
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
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
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