The render props pattern has been a popular way to share logic between components. Since React 16.8 custom hooks are a more elegant way of sharing logic between components. So, no need for render props now then? No! Render props are still useful for building reusable components ...
What is a render prop?
A render prop is a prop that is a function that renders something – i.e. a function that returns JSX:
interface Props { | |
... | |
renderItem?: (item: string) => JSX.Element; | |
renderHeader?: () => JSX.Element; | |
} |
They can be used to delegate the rendering of bits of a component to the consumer of the component. This can make a component very flexible and highly reusable.
Every component already has a render prop!
Every React component has a children
prop:
export const Card: FC = ({ children }) => ( | |
<div className="card"> | |
{children} | |
</div> | |
); |
This is a render prop! In the above example the children
prop allows the consumer of the component to render the content of the card.
<Card> | |
<p>Some interesting text</p> | |
<button>Click me</button> | |
</Card> |
Above is an example of consuming the Card
component. The paragraph and button elements nested inside Card
are picked up as the children
prop and rendered inside the card div:
Creating a render prop
We can create our own render prop:
interface Props { | |
title?: string; | |
renderHeader?: () => JSX.Element; | |
} | |
export const Card: FC<Props> = ({ children, title, renderHeader }) => ( | |
<div className="card"> | |
<div className="card-header"> | |
{renderHeader ? renderHeader() : title !== undefined ? title : null} | |
</div> | |
<div className="card-content">{children}</div> | |
</div> | |
); |
We have extended the Card
component to have a header. The consumer can override default appearance using the renderHeader
render prop:
<Card renderHeader={() => <h3>A custom header</h3>}> | |
<p>Some interesting text</p> | |
<button>Click me</button> | |
</Card> |
Above is an example of consuming the Card
component supplying the header using the renderHeader
prop. We simply assign the renderHeader
prop to an inline arrow function that returns a h3
containing our title.
We are now starting to understand the power of render props and how it makes a component super flexible and reusable.
Reusable list
A common use case for render props are list components:
interface Props { | |
data: string[]; | |
renderItem?: (item: string) => JSX.Element; | |
renderHeader?: () => JSX.Element; | |
} | |
export const List: FC<Props> = ({ data, renderItem, renderHeader }) => ( | |
<div className="list"> | |
<div className="list-header">{renderHeader && renderHeader()}</div> | |
<ul> | |
{data.map(item => ( | |
<li key={item}>{renderItem ? renderItem(item) : item}</li> | |
))} | |
</ul> | |
</div> | |
); |
Above is a simple List
component that has render props for the list header and list items. Notice that renderItem
has a parameter for the data item to be used when rendering the item.
<List | |
data={["Fred", "Bob", "Jane"]} | |
renderHeader={() => <h3>Names</h3>} | |
renderItem={item => ( | |
<div> | |
<span style={{ marginRight: "10px" }}>{item}</span> | |
<button>Click me</button> | |
</div> | |
)} | |
/> |
Above is an example of consuming the List
component. We render the list header using a h3
using the renderHeader
prop. We render each data item in a span
with a Click me button alongside it using the renderItem
prop. Below is the result:
Nice!
Wrap up
Render props are still really useful when we are creating highly reusable components that allow the consumer to render custom elements.
Every React component automatically has a children
prop for allowing the consumer to render a single bit of the component.
We can create our own render props in a component where we want to allow the consumer to render different bits of a component.
Render props can take in parameters which is useful when the render prop is being used to render a collection of data items.
Originally published at https://www.carlrippon.com/render-props-are-still-useful on August 28, 2019.
Top comments (1)
Nice article! Thanks for sharing)