Higher order components are something that is brought into React version 16. Whereas there could be many codebases still using React 15 and they would need an alternative solution to Mixin's which would cause a lot of side effects and restricts the usage of lifecycle methods due to shared state and shared lifecycle methods.
Shared state is something that doesn't follow Functional Programming principles and leads to lot of bugs.
Lets see how we can solve this problem without using Higher order components but a pattern called composite components:
Say we have an application similar to the one we have in the Apple's AppStore that prompts the user for a password whenever the user decides to download an application. This functionality would be needed in several places and it is quite generic.
So to implement this behavior in React we need to have a state for managing the password form and also its own API for verifying the password.
This is one good example where we could Compose our components with the dependencies needed.
The functionalities of rendering the password form and verifying it will be handled by the Composer component.
Now this is how our components would look like:
const ComposeWithPasswordVerification=React.createClass({
propTypes:{
component:React.PropTypes.any.isRequired,
componentProps:React.PropTypes.object,
passwordVerificationText:React.PropTypes.string
},
getDefaultProps:function(){
return {
componentProps:{},
passwordVerificationText:"Please re-enter your password to continue"
};
},
getInitialState:function(){
return {
showPasswordVerificationForm:false,
password:"",
isPasswordVerified:false
};
},
render:function(){
return <this.props.component componentProps={...this.props.componentProps} renderPasswordForm={this.renderPasswordForm} isPasswordVerified={this.state.isPasswordVerified} />
},
renderPasswordForm:function(){
return <PasswordVerificationForm onPasswordChange={this.onPasswordChange} showForm={this.state.showPasswordVerificationForm} onPasswordVerified={this.onPasswordVerified} password={this.state.password}/>
},
onPasswordChange:function(password){
this.setState({
password:password
});
},
onPasswordVerified:function(isVerified){
this.setState({
showPasswordVerificationForm:!isVerified,
isVerified:isVerified
});
}
});
As we see here all the state that goes into managing the form is handled by the composer and its passed as a prop to the component that is to be composed with this functionality.
This is where it stands out, as it follows the Single Responsibility Principle of functional programming.
If you look at the Consumer component there is another component(PasswordVerificationForm) used to render the form, this component is actually a functional component without any state and its a fully controlled component.
Now the method to render this component is passed as a prop to the consumer by composer, so the form can be rendered on demand by the consumer.
The PasswordVerificationForm component would look like following:
const PasswordVerificationForm=React.createClass({
propTypes:{
password:React.PropTypes.string,
onPasswordChange:React.PropTypes.func,
showForm:React.PropTypes.bool,
onPasswordVerified:React.PropTypes.func,
passwordVerificationText:React.PropTypes.string,
},
getDefaultProps:function(){
return {
password:"",
onPasswordChange:function(){},
showForm:React.PropTypes.bool,
onPasswordVerified:function(){},
passwordVerificationText:""
};
},
render:function(){
return this.props.showForm ? (
<div className="password-verification-form">
<div className="text">{this.props.passwordVerificationText}</div>
<input type="password" onChange={this.onChange} value={this.props.password} />
<button onClick={this.onVerify}>Verify</button>
</div>
) : null;
},
onChange:function(e){
this.props.onPasswordChange(e.target.value);
},
onVerify:function(e){
e.stopPropagation();
//Make the api call here and call the onVerified prop here with the true or false returned by the api
let isPasswordVerified=api.verifyPassword(this.props.password);
this.props.onPasswordVerified(isPasswordVerified);
}
});
Now lets see the consumption of the Composing component:
const AppStore=React.createClass({
propTypes:{
/* may contain other props */
renderPasswordForm:React.PropTypes.func,
isPasswordVerified:React.PropTypes.bool
},
getDefaultProps:function(){
return {
renderPasswordForm:function(){ return ""},
isPasswordVerified:false
};
},
render:function(){
return (
<div className="app-store-ctr">
{/* App store dom contents here */}
<button onClick={this.downloadApp}>Download</button>
{this.props.renderPasswordForm()}
</div>
);
},
downloadApp:function(){
if(this.props.isPasswordVerified){
api.downloadApp({appName:"sampleApp"});
}else{
return;
}
}
});
const AppContainer=React.createClass({
render:function(){
return (
<div className="app-container">
{/* Other contents of app */}
<ComposeWithPasswordVerification component={AppStore} componentProps={{/*props of AppStore component here */}} />
<ComposeWithPasswordVerification component={PasswordReset} componentProps={{/*props of PasswordReset component here */}} />
<ComposeWithPasswordVerification component={UndoPurchase} componentProps={{/*props of UndoPurchase component here */}} />
<ComposeWithPasswordVerification component={AnyOtherComponentThatRequiresPasswordVerification} componentProps={{/*props of AnyOtherComponentThatRequiresPasswordVerification component here */}} />
</div>
)
}
});
Here the AppStore component is one of the examples which is going to be composed with the PasswordVerification feature and AppContainer is the Parent component of all which acts as a container component.
The other components PasswordReset, UndoPurchase are just provided as sample examples to understand the use of Composition in terms of Reusability.
Hope you all understood the concept behind the Component Composition and please do reach out to me in comments for any queries or doubts.
If you are using React 16 and above, then higher order components would serve the same purpose whereas this is a good workaround for getting rid of mixins in your React 15 App.
Top comments (0)