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.
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.
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.
Dismiss
component, that means we’ll have to hardcode some HTML.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.
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:
Dismiss
componentchildren
being renderedYou 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:
Dismiss
can communicate with the component being rendered and vice versaDismiss
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.
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> ) }
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.
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.
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.
Install LogRocket via npm or script tag. LogRocket.init()
must be called client-side, not
server-side
$ npm i --save logrocket // Code: import LogRocket from 'logrocket'; LogRocket.init('app/id');
// Add to your HTML: <script src="https://cdn.lr-ingest.com/LogRocket.min.js"></script> <script>window.LogRocket && window.LogRocket.init('app/id');</script>
Hey there, want to help make our blog better?
Join LogRocket’s Content Advisory Board. You’ll help inform the type of content we create and get access to exclusive meetups, social accreditation, and swag.
Sign up nowExplore use cases for using npm vs. npx such as long-term dependency management or temporary tasks and running packages on the fly.
Validating and auditing AI-generated code reduces code errors and ensures that code is compliant.
Build a real-time image background remover in Vue using Transformers.js and WebGPU for client-side processing with privacy and efficiency.
Optimize search parameter handling in React and Next.js with nuqs for SEO-friendly, shareable URLs and a better user experience.
One Reply to "React Reference Guide: Render props"
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 (
)
}
“`