This post is part of our course. If you enjoy this post, check it out. React Fundamentals If you want to read a similar article but about Higher-Order Components instead, check out React Higher-Order Components _There’s two important things to note before we get started. First, what we’re going to talk about is just a pattern. It’s not really even a React thing as much as it is a component architecture thing. Second, this isn’t required knowledge to build a React app. You could skip this post, never learn what we’re about to talk about, and still build fine React applications. However, just like building anything, the more tools you have available the better the outcome will be. If you write React apps, you’d be doing yourself a disservice by not having this in your “toolbox”._You can’t get very far into studying software development before you hear the (almost cultish) mantra of or . Sometimes it can be taken a bit too far, but for the most part, it’s a worthwhile goal. In this post we’re going to look at the most popular pattern for accomplishing DRY in a React codebase, Higher-Order Components. However before we can explore the solution, we must first fully understand the problem. Don't Repeat Yourself D.R.Y Let’s say we were in charge of recreating a dashboard similar to Stripe’s. As most projects go, everything goes great until the very end. Just when you think you’re about to be done, you notice that the dashboard has a bunch of different tooltips that need to appear when certain elements are hovered over. There are a few ways to approach this. The one you decide to go with is to detect the hover state of the individual components and from that state, show or not show the tooltip. There are three components you need to add this hover detection functionality to — , and . Info TrendChart DailyChart Let’s start with . Right now it’s just a simple SVG icon. Info class Info extends React.Component {render() {return (<svgclassName="Icon-svg Icon--hoverable-svg"height={this.props.height}viewBox="0 0 16 16" width="16"><path d="M9 8a1 1 0 0 0-1-1H5.5a1 1 0 1 0 0 2H7v4a1 1 0 0 0 2 0zM4 0h8a4 4 0 0 1 4 4v8a4 4 0 0 1-4 4H4a4 4 0 0 1-4-4V4a4 4 0 0 1 4-4zm4 5.5a1.5 1.5 0 1 0 0-3 1.5 1.5 0 0 0 0 3z" /></svg>)}} Now we need to add functionality to it so it can detect whether it’s being hovered over or not. We can use the and mouse events that come with React. The function we pass to will be invoked when the component is hovered over and the function we pass to will be invoked when the component is no longer being hovered over. To do this the React way, we’ll add a state property to our component so that we can cause a re-render when the state changes, showing or hiding our tooltip. onMouseOver onMouseOut onMouseOver onMouseOut hovering hovering class Info extends React.Component {state = { hovering: false }mouseOver = () => this.setState({ hovering: true })mouseOut = () => this.setState({ hovering: false })render() {return (<>{this.state.hovering === true? <Tooltip id={this.props.id} />: null}<svgonMouseOver={this.mouseOver}onMouseOut={this.mouseOut}className="Icon-svg Icon--hoverable-svg"height={this.props.height}viewBox="0 0 16 16" width="16"><path d="M9 8a1 1 0 0 0-1-1H5.5a1 1 0 1 0 0 2H7v4a1 1 0 0 0 2 0zM4 0h8a4 4 0 0 1 4 4v8a4 4 0 0 1-4 4H4a4 4 0 0 1-4-4V4a4 4 0 0 1 4-4zm4 5.5a1.5 1.5 0 1 0 0-3 1.5 1.5 0 0 0 0 3z" /></svg></>)}} Looking good. Now we need to add the same functionality to our other two components, and . If it’s not broke, don’t fix it. Our hover logic for worked great so let’s use that same code again. TrendChart DailyChart Info class TrendChart extends React.Component {state = { hovering: false }mouseOver = () => this.setState({ hovering: true })mouseOut = () => this.setState({ hovering: false })render() {return (<>{this.state.hovering === true? <Tooltip id={this.props.id}/>: null}<Charttype='trend'onMouseOver={this.mouseOver}onMouseOut={this.mouseOut}/></>)}} You probably know the next step. We can do the same thing for our final component. DailyChart class DailyChart extends React.Component {state = { hovering: false }mouseOver = () => this.setState({ hovering: true })mouseOut = () => this.setState({ hovering: false })render() {return (<>{this.state.hovering === true? <Tooltip id={this.props.id}/>: null}<Charttype='daily'onMouseOver={this.mouseOver}onMouseOut={this.mouseOut}/></>)}} And with that, we’re all finished. You may have written React like this before. It’s not the end of the world (#shipit), but it’s not very “DRY”. As you saw, we’re repeating the exact same hover logic in every one of our components. At this point, the should be pretty clear, . So what’s the ? Well before we get to that, we need to get a refresh on two fundamental aspects of React. They are components which don’t render UI and passing functions as props. problem we want to avoid duplicating our hover logic anytime a new component needs it solution No UI Components In most cases whenever you build a React component, the end goal it to show some UI to the screen. However, that doesn’t always need to be the case. It’s entirely reasonable to have components which act as “Wrapper” components. They’re responsible for handling some logic but instead of rendering their own UI, they just render another component passing it data. class Users extends React.Component {state = {users: null}componentDidMount() {getUsers().then((users) => {this.setState({ users })})}render() {<Grid data={this.state.users} />}} In the example above is responsible for getting the users, then passing them to the component. It doesn’t have its own UI, instead, it uses the UI from the component. Users Grid Grid Passing functions as props As you know, props are part of React’s component API that allow you to pass data into a component. <User id='tylermcginnis' /> Then inside of the component, the object would have an property referencing the string . User props id tylermcginnis function User (props) {const id = props.id // tylermcginnis} Now what if instead of passing a string as a prop, we passed a function? <User id={() => 'tylermcginnis'} /> Now the object still has an property, only now instead of being a string, it references a function. So in order to get the id, we need to invoke the function. props id function User (props) {const id = props.id() // tylermcginnis} Now what if we wanted to pass the function prop some data? Well, it’s just a function so we could do it just like we normally would by passing it an argument. function User (props) {const id = props.id(true) // tylermcginnis} <User id={(isAuthed) => isAuthed === true ? 'tylermcginnis' : null} /> OK… but what do both of these have to do with the problem we saw earlier of duplicating our hover logic anytime a new component needs it? Well we can combine both of these simple concepts in order to solve our problem. First, we want to create a “Wrapper” component which is responsible for managing the hover state. We’ll call it, naturally, and it’ll contain all the hover logic that we had to duplicate from earlier. Hover class Hover extends React.Component {state = { hovering: false }mouseOver = () => this.setState({ hovering: true })mouseOut = () => this.setState({ hovering: false })render() {return (<div onMouseOver={this.mouseOver} onMouseOut={this.mouseOut}> </div> ) }} The next question becomes what should render? This is where are function prop knowledge comes into play. Let’s have receive a prop called . This prop is going to be a function that we can pass the state to and it will return some UI. Hover Hover render render hovering <Hover render={(hovering) =><div>Is hovering? {hovering === true ? 'Yes' : 'No'}<div>} /> Now the last change we need to make is in our component. All we need to do is invoke passing it . Hover this.props.render this.state.hover class Hover extends React.Component {state = { hovering: false }mouseOver = () => this.setState({ hovering: true })mouseOut = () => this.setState({ hovering: false })render() {return (<div onMouseOver={this.mouseOver} onMouseOut={this.mouseOut}>{this.props.render(this.state.hovering)}</div>)}} Well would you look at that. Now that we have our component, any time we need a component to be aware of its hover state, we just wrap it inside of a s prop. Hover Hover render Finally let’s head back to the original code we had and see how we no longer have to duplicate all the hover logic since we have our component. Hover This is what we had before. class Info extends React.Component {state = { hovering: false }mouseOver = () => this.setState({ hovering: true })mouseOut = () => this.setState({ hovering: false })render() {return (<>{this.state.hovering === true? <Tooltip id={this.props.id} />: null}<svgonMouseOver={this.mouseOver}onMouseOut={this.mouseOut}className="Icon-svg Icon--hoverable-svg"height={this.props.height}viewBox="0 0 16 16" width="16"><path d="M9 8a1 1 0 0 0-1-1H5.5a1 1 0 1 0 0 2H7v4a1 1 0 0 0 2 0zM4 0h8a4 4 0 0 1 4 4v8a4 4 0 0 1-4 4H4a4 4 0 0 1-4-4V4a4 4 0 0 1 4-4zm4 5.5a1.5 1.5 0 1 0 0-3 1.5 1.5 0 0 0 0 3z" /></svg></>)}} class TrendChart extends React.Component {state = { hovering: false }mouseOver = () => this.setState({ hovering: true })mouseOut = () => this.setState({ hovering: false })render() {return (<>{this.state.hovering === true? <Tooltip id={this.props.id}/>: null}<Charttype='trend'onMouseOver={this.mouseOver}onMouseOut={this.mouseOut}/></>)}} class DailyChart extends React.Component {state = { hovering: false }mouseOver = () => this.setState({ hovering: true })mouseOut = () => this.setState({ hovering: false })render() {return (<>{this.state.hovering === true? <Tooltip id={this.props.id}/>: null}<Charttype='daily'onMouseOver={this.mouseOver}onMouseOut={this.mouseOut}/></>)}} function App () {return (<><Info /><TrendChart /><DailyChart /></>)} And now with our component, instead of each component having to duplicate the hover logic, we can wrap each one inside of the prop we pass to and then pass down the argument as a prop. Hover render Hover hovering function Info (props) {return (<>{props.hovering === true? <Tooltip id={this.props.id} />: null}<svgonMouseOver={this.mouseOver}onMouseOut={this.mouseOut}className="Icon-svg Icon--hoverable-svg"height={this.props.height}viewBox="0 0 16 16" width="16"><path d="M9 8a1 1 0 0 0-1-1H5.5a1 1 0 1 0 0 2H7v4a1 1 0 0 0 2 0zM4 0h8a4 4 0 0 1 4 4v8a4 4 0 0 1-4 4H4a4 4 0 0 1-4-4V4a4 4 0 0 1 4-4zm4 5.5a1.5 1.5 0 1 0 0-3 1.5 1.5 0 0 0 0 3z" /></svg></>)} function TrendChart (props) {return (<>{props.hovering === true? <Tooltip id={this.props.id}/>: null}<Charttype='trend'onMouseOver={this.mouseOver}onMouseOut={this.mouseOut}/></>)} function DailyChart (props) {return (<>{props.hovering === true? <Tooltip id={this.props.id}/>: null}<Charttype='daily'onMouseOver={this.mouseOver}onMouseOut={this.mouseOut}/></>)} function App () {return (<><Hover render={(hovering) =><Info hovering={hovering}>}> <Hover render={(hovering) => <TrendChart hovering={hovering}> }> <Hover render={(hovering) => <DailyChart hovering={hovering}> }> </> )} This pattern, as you probably guessed by now, is called . Summarized in the React docs, “the term render prop refers to a technique for sharing code between React components using a prop whose value is a function”. Render Props Another way to utilize the render props pattern is with React’s prop. If you’ve never used before, it’s just like any other prop, accept instead of you passing it explicitly to the component, React automatically does it for you and it reference whatever is in the body of the component. children props.children function User (props) {return (<div>{props.children}</div>)} <User>This is props.children</User> In the example above, what’s going to get rendered to the UI is a with the words inside of it. div This is props.children Now what if instead of having be a string, it was a function? Just as we saw earlier, we’d need to invoke it to get the value. props.children function User (props) {return (<div>{props.children()}</div>)} <User>{() => This is props.children}</User> With our newly formed knowledge of , let’s update our examples from earlier. Now instead of having a prop, let’s get rid of that all together and use instead. props.children Hover render props.children function App () {return (<><Hover>{(hovering) => <Info hovering={hovering}>}</Hover> <Hover> {(hovering) => <TrendChart hovering={hovering}>} </Hover> <Hover> {(hovering) => <DailyChart hovering={hovering}>} </Hover> </> )} Looks good. Now we need to update so instead of invoking , it invokes . Hover this.props.render this.props.children class Hover extends React.Component {state = { hovering: false }mouseOver = () => this.setState({ hovering: true })mouseOut = () => this.setState({ hovering: false })render() {return (<div onMouseOver={this.mouseOver} onMouseOut={this.mouseOut}>{this.props.children(this.state.hovering)}</div>)}} Nice. Is this better? Not really, it’s just different. I prefer it, but there’s nothing objectively better about it. If you read our post about , you’ll be familiar with how HOCs have some pitfalls. The biggest one was with inversion of control and naming collisions. Because you have to pass your component over to the Higher-Order component, you have no control over how it’s rendered. We looked at an example with React Router’s HOC. will pass , , and props to the wrapped component whenever it renders. Higher Order Components withRouter withRouter match location history class Game extends React.Component {render() {const { match, location, history } = this.props // From React Router ... }} export default withRouter(Game) If our Game component is already receiving , , or as a prop, we’re going to have a naming collision and it’s going to be a hard bug to track down. match location history Does this same pitfall occur with Render Props? Nope. Instead of handing over the component, we hand over a function. Then, when that function is invoked, it’ll be passed the data we need. No inversion of control and no naming collisions since we can decide how the component is rendered. <Hover>{(hovering) => {// We can do whatever we want here.// We decide how and when to render the componentreturn <Info anyNameWeWant={hovering} />}}</Hover> Now the big question is, should you use Render Props or Higher Order Components? Well, that’s up to you. You now know how to use them both which means you have enough information to make an informed decision for yourself. This was originally published at tylermcginnis.com as part of their React Fundamentals course. Follow Tyler on Twitter