Reusing React Component logic with Render Props
If you have used React for some time, Reusing Components is one of the things you would always do. The most obvious way most of us do is using Higher Order Component.
Having read through Chinonso Johnson’s article on Reusing React component logic with HOC. I thought about achieving the same result but using Render props instead. This is what we will cover in this post.
Render Props
According to Michael Jackson, a render prop is a function prop that a component uses to know what to render. By the way, if you have not watched Michael Jackson’s video on render props, please do.
To start, imagine we have a Signin and Signup Component both having the onChange event handler that changes the state
SignIn Component
import React from 'react';
class SignIn extends React.Component {
state = {
auth: {
email: '',
password: '',
},
};
onChange = ({ target }) => {
this.setState({
auth: {
...this.state.auth,
[target.name]: target.value,
},
});
};
onSubmit = (event) => {
event.preventDefault();
// do something here
console.log(this.state.auth);
};
render() {
const { email, password } = this.state.auth;
return (
<form onSubmit={this.onSubmit}>
<h4> Sign In </h4>
<input type="email" name="email" onChange={this.onChange} value={email} />
<label htmlFor="email">Email</label>
<input type="password" name="password" onChange={this.onChange} value={password} />
<label htmlFor="password">Password</label>
<button type="submit">Submit</button>
</form>
);
}
}
export default SignIn;
Signup Component
import React from 'react';
class Signup extends React.Component {
state = {
auth: {
email: '',
password: '',
},
};
onChange = ({ target }) => {
this.setState({
auth: {
...this.state.auth,
[target.name]: target.value,
},
});
};
onSubmit = (event) => {
event.preventDefault();
// do something here
console.log(this.state.auth);
};
render() {
const { email, password, name, username } = this.state.auth;
return (
<form onSubmit={this.onSubmit}>
<h4> Sign Up </h4>
<label htmlFor="email">Name</label>
<input type="text" name="name" onChange={this.onChange} value={name} />
<label htmlFor="email">Username</label>
<input type="text" name="username" onChange={this.onChange} value={username} />
<label htmlFor="email">Email</label>
<input type="email" name="email" onChange={this.onChange} value={email} />
<input type="password" name="password" onChange={this.onChange} value={password} />
<label htmlFor="password">Password</label>
<button type="submit">Submit</button>
</form>
);
}
}
export default Signup;
As you can see, repetitive onChange and onSubmit functions in both Signin and Signup components. We don’t want to do this, as it defeats the purpose of DRY (Don’t Repeat Yourself)
Now let’s create a reusable component that can be used to share this logic with both the Signin and Signup components.
Auth Render
import React, { Component } from 'react';
class authRender extends Component {
state = { auth: {} };
onChange = ({ target }) => {
this.setState({
auth: {
...this.state.auth,
[target.name]: target.value,
},
});
};
onSubmit = (e) => {
e.preventDefault();
console.log(this.state.auth);
};
getStateAndHelpers = () => {
return {
onChange: this.onChange,
onSubmit: this.onSubmit,
auth: this.state.auth,
};
};
render() {
return this.props.children(this.getStateAndHelpers());
}
}
export default authRender;
What we have done in the renderProps file is that we just took the logic from both Signin and Signup components into a separate component that simply renders the children. You can then pass all the props to be available inside the children function, although I decided to separate it into its own function. This approach is recommended by Kent C. Dodds
How do we use it
It is highly recommended to separate your components into Containers and Presentational Components. Basically, Containers have state either a component state or a state management like redux.
We are going to create a new file inside the container where it will have all the state and the event handlers.
Signin Container
import React, { Component } from 'react';
import AuthRender from './renderProps';
import Signin from './Signin';
class signinContainer extends Component {
render() {
return (
<div>
<Auth>{({ onChange, onSubmit }) => <Signin onChange={onChange} onSubmit={onSubmit} />}</Auth>
</div>
);
}
}
Refactored Signin component
import React from 'react';
const SignIn = ({ onChange, onSubmit }) => (
<form onSubmit={onSubmit}>
<h4> Sign In </h4>
<label htmlFor="email">Email</label>
<input type="email" name="email" onChange={onChange} className="validate" />
<label htmlFor="password">Password</label>
<input type="password" name="password" onChange={onChange} className="validate" />
<button type="submit">Submit</button>
</form>
);
export default SignIn;
As we can see, our Signin component has been refactored into functional components making it easy to test. Of course, this is not the best use case for using render props, since there are other simpler ways of achieving the same without HOC or render props.
Benefits of using Render Props
We don’t have to wonder where our state or props are coming from. We can see them in the render prop’s argument list.
There is no automatic merging of property names, so there is no chance for a naming collision. Michael Jackson
This is true because imagine having multiple HOC on one component, you might be lost, making it difficult to know where the props are coming from.
Conclusion
Render props are amazing, it can do all H0C with a regular component. I encourage you to watch Michael ’s talk to understand it more and hope you will refactor a lot of your HOC to use regular component with render props.