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:
- Why do we need render props?
- Implementing render props
- Implementing other props
- Implementing higher-order components (HOCs)
- Caveats
- Sandbox demo
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.

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.
- 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.
LogRocket: Full visibility into your production React apps
Debugging React applications can be difficult, especially when users experience issues that are hard 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 combines session replay, product analytics, and error tracking – empowering software teams to create the ideal web and mobile product experience. What does that mean for you?
Instead of guessing why errors happen, or asking users for screenshots and log dumps, LogRocket lets you replay problems as if they happened in your own browser to quickly understand what went wrong.
No more noisy alerting. Smart error tracking lets you triage and categorize issues, then learns from this. Get notified of impactful user issues, not false positives. Less alerts, way more useful signal.
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 — start monitoring for free.
The caveats section says that
“`
const DismissableContent = () => {
const content = (dismiss) =>
return (
)
}
“`
avoids extra re-renders compared to
“`
const DismissableContent = () => {
return (
} />
)
}
“`
but both variants still recreate the inline function on every re-render. What should work is one of the following:
“`
const content = (dismiss) =>
const DismissableContent = () => {
return (
)
}
“`
“`
const DismissableContent = () => {
const content = useCallback((dismiss) => , []);
return (
)
}
“`