Ovie Okeh Programming enthusiast, lover of all things that go beep.

React Reference Guide: Render props

5 min read 1421

LogRocket React Reference Guide: Render props

A render prop is simply a function prop that is called in a render method. To truly understand what this means, let’s learn why such a pattern exists in the first place, then explore the various implementations.

Jump ahead:


React has made building interactive applications a lot more manageable. We’re able to break up our app into components, each with their own encapsulated logic and styling. It’s great — it means we’re able to work on lots of smaller problems instead of one really large one.

This method of building applications works really well because of abstractions. A component in an application is just an abstraction of logic, data, and, in some cases, styling.

const Button = () => (...)

The Button component above is an abstraction of the logic and styles required to make a button show up on a page. Now, every time we want a button, we simply use this abstraction — no need to reinvent the wheel.

When we build apps this way, a new challenge arises: sometimes we want to share logic or data between components. We also want to do this in a way that is clean and composable. Just as we’re able to abstract logic and data into components, we also want to be able to compose these components to form larger building blocks.

Four paragraphs in and we still haven’t gotten to render props yet.

Put simply, a render prop is a function prop that is called in a render method. But to truly understand what this means, we need to know why such a pattern exists in the first place.


Why do we need render props?

Let’s take a legitimate problem we might face in the real world. Say we wanted to have a UI element that we could dismiss with an animation.

Delete from table UI
Delete from table by Alex Lauderdale.

What we want here is a component that can render anything, but we want to keep this dismissal animation. We could simply use the children prop and pass whatever we wanted to render as a child.

const Dismiss = ({ children }) => {
  return (
    <Fragment>
      {children}
    </Fragment>
  )
}

That would work, but it’s limited.

We made a custom demo for .
No really. Click here to check it out.

  • How would we fire the dismiss event from within the component being rendered? If we decide to fire it from the Dismiss component, that means we’ll have to hardcode some HTML.
  • What if we wanted to set up different actions to fire on the dismiss event?
  • Is there any way to pass data as props to the children being rendered?

In situations like this, we need something a bit more flexible than the children prop method. What we really want is to encapsulate the dismissal functionality so that we can share it between components.

With render props, we could render anything we wanted, just like the children prop method. But we will also be able to pass props to whatever is being rendered.

This is the main purpose of render props — being able to communicate with any rendered component without having to couple implementation details. This is possible because the render function now comes in as a prop, and you can pass data to it as arguments when you call it.

This will become clearer when we take a look at a practical use case for render props (based on WAVE’s modal component). We’ll look at a possible solution to the problem we raised initially using the children prop method. Then, we’ll look at a more flexible solution with render props.

Now that you know the purpose of props, see how devs use them for modern reusable components.


Implementing render props

const Dismiss = ({ children }) => {
  const dismiss = () => {
    ...code to implement dismissal animations etc
  }

  return (
    <div>
      {children}
      <button onClick={dismiss}>dismiss</button>
    </div>
  )
}

const DismissableContent = () => {
  return (
    <Dismiss>
      ...code
    </Dismiss>
  )
}

Above, we have a Dismiss component that renders whatever is passed as the children prop but also contains code to implement the dismiss animation. Below it is how we’d use such a component. Notice that with this implementation:

  • You can only fire the dismiss event from within the Dismiss component
  • You can’t pass data to the children being rendered
  • You have to account for the extra div in your layouts

You can see some of the limitations in sharing functionality this way. Now let’s look at the same solution, but with render props:

const Dismiss = (props) => {
  const dismiss = () => {
    ...code to implement dismissal animations etc
  }

  return props.render(dismiss)
}

const DismissableContent = () => {
  return (
    <Dismiss render={
      dismiss => <Content dismiss={dismiss} />
    } />
  )
}

Apart from the code being smaller, there are some more important advantages to this method.

We have moved the content of the render function from being hardcoded in the Dismiss component to accepting it from the props. By simply calling props.render(), the component is able to render whatever is returned from the render prop. This comes with some implications:

  • Since the render prop is a function, we can pass data to it as arguments. This means that Dismiss can communicate with the component being rendered and vice versa
  • You don’t have to account for any extra divs from Dismiss because it can be pure and not return its own HTML (most times, though, you’d return some accompanying HTML and styling)

We can now see how this solves the issues we had using the children prop method. This is much cleaner and more flexible by every metric.


Implementing other props

Another advantage of render props is that the prop doesn’t have to be called render. You could call it whatever you want as long as the prop is invoked in the render method. This leads to interesting implementations, like this:

const Dismiss = ({ children }) => {
  const dismiss = () => {
    ...code to implement dismissal animations etc
  }

  return children(dismiss)
}

const DismissableContent = () => {
  return (
    <Dismiss>
      {(dismiss) => (
        <Content dismiss={dismiss} />
      )}
    </Dismiss>
  )
}

Move on to mastering the basics of props with this comprehensive tutorial.


Implementing higher-order components (HOCs)

You may have noticed by now that render props also allow us to implement HOCs with minimal effort. Below, you can see a withDismiss HOC that takes in a component and enhances a component with the dismiss functionality.

const withDismiss = (Component) =>
  (props) => (
    <Dismiss>
      {(dismiss) => (
        <Component dismiss={dismiss} {...props} />
      )}
    </Dismiss>
  )

This is so easy because of the compositional nature of components in React. Because we’ve been able to encapsulate the dismiss logic into a component with render props functionality, composing new components with the same functionality becomes effortless.

Dive deeper into HOCs with a more advanced use case: fetching data.


Caveats

There’s an important edge case to keep in mind when using the render props pattern if you use React.PureComponent. For a quick refresher, PureComponent can provide small performance gains because it implements shouldComponentUpdate internally through a shallow props and state comparison.

The important thing to note here is the shallow props comparison. Most of the time, when you use render props, you’ll be passing an anonymous function to the render prop.

class Dismiss extends React.PureComponent {
  dismiss = () => {
    ...code to implement dismissal animations etc
  }

  render() {
    return this.props.render(dismiss)
  }
}

const DismissableContent = () => {
  return (
    <Dismiss render={
      dismiss => <Content dismiss={dismiss} /> // will be different every render
    } />
  )
}

In this example, every time DismissableContent renders, it generates a new value for the render prop, and this fails the shallow comparison done by React.PureComponent.

If it is important to you that this doesn’t happen — e.g., maybe Dismiss contains some intensive code — you could fix it by moving the render prop value into a named function, like this:

class Dismiss extends React.PureComponent {
  ...same implementation as above
}

const DismissableContent = () => {
  const content = (dismiss) => <Content dismiss={dismiss} />

  return (
    <Dismiss render={content} />
  )
}

This will ensure that the content value becomes an instance of the DismissableContent component and is not regenerated on every render.

Explore why these caveats have led devs to start using custom Hooks over render props.


If you want a playground to practice with render props, you can fork the CodeSandbox below and try to get a practical feel for working with the pattern.

Full visibility into production React apps

Debugging React applications can be difficult, especially when users experience issues that are difficult to reproduce. If you’re interested in monitoring and tracking Redux state, automatically surfacing JavaScript errors, and tracking slow network requests and component load time, try LogRocket.

LogRocket is like a DVR for web apps, recording literally everything that happens on your React app. Instead of guessing why problems happen, you can aggregate and report on what state your application was in when an issue occurred. LogRocket also monitors your app's performance, reporting with metrics like client CPU load, client memory usage, and more.

The LogRocket Redux middleware package adds an extra layer of visibility into your user sessions. LogRocket logs all actions and state from your Redux stores.

Modernize how you debug your React apps — .

Ovie Okeh Programming enthusiast, lover of all things that go beep.

Leave a Reply