The render props pattern in React is a powerful technique for sharing code between components. It involves using a prop whose value is a function. A component with a render prop takes this function, which returns a React element, and calls it instead of implementing its render logic. This pattern offers significant benefits, such as enhanced reusability and flexibility in your components, making your code more maintainable and easier to understand.
Let’s understand the basics. You have a component named Counter. Let’s put the render props pattern into action with a practical example. We’ll use it to share counterlogic between two components: ClickCounter and HoverCounter. This real-world example, which you can relate to, will help you see the practicality and power of the render props pattern in solving real development challenges.
Don’t worry. We’ll break down the concept of render props into three parts to provide a clear and straightforward understanding, making the learning process easier and more manageable.
- View side components of
click
andhover
counters. - The
Counter
component takes the render function as a prop. - The calling side calls the Counter component with a render function.
1. View Side Components
ClickCounter Component
const ClickCounter = ({ count, incrementCount }) => {
return (
);
}
- This component displays the number of times the button clicks.
- It receives two props:
count
(the current count) andincrementCount
(a function to increment the count).
HoverCounter Component
const HoverCounter = ({ count, incrementCount }) => {
return (
Hovered {count} times
);
}
- This component renders a heading that displays the number of times it has been hovered over.
- It also receives the
count
andincrementCount
props.
2. Counter Component with Render Props
Counter Component
const Counter = ({ render }) => {
const [count, setCount] = useState(0);
const countHandler = () => {
setCount(prev => prev + 1);
};
return render(count, countHandler);
};
export default Counter;
- This component maintains the counter logic.
- It uses the
useState
hook to keep track of thecount
state. - The
countHandler
function increments thecount
state. - The render function is called and returned with the current
count
and thecountHandler
function, allowing any component to use this counter logic.
3. Calling Side
App Component
const App = () => {
return (
<>
(
)}
/>
(
)}
/>
>
);
};
- The
App
component uses theCounter
component twice with different render functions. - The first
Counter
component renders aClickCounter
, passing down thecount
andincrementCount
as props. - The second
counter
component renders aHoverCounter
, passing down thecount
andincrementCount
as props.
When to use
- You need to share logic between multiple components but don’t want to use higher-order components (HOCs) or find them unsuitable for your use case.
- You need reusable functionality like form handling, data fetching, or animations.
- React render props are handy when components need to be highly customizable, and you want to allow users to define how certain aspects of the component should render or behave.
- When you need more control over the rendering process than HOCs provide.
Advantages
- Reusability: Multiple components can share logic without duplication.
- This pattern offers a high degree of flexibility and control: Components using render props can be easily customized, allowing for more dynamic rendering and empowering you to create elements that suit your needs.
- Separation of Concerns: Keeps logic and UI concerns separate, making the components more straightforward to understand and maintain.
Disadvantages
- Complexity: Introduces additional complexity, making the code harder to read, especially for those unfamiliar with the pattern.
- Performance: This can lead to performance issues due to the creation of new functions on every render.
- Verbosity: It can complicate the component structure, especially with deeply nested components.
Example
Let’s create a simple example using the Render Props pattern to display a collection of products in different locations of our application.
First, let’s declare a list of products:
export const allProducts = [
{
id: 1,
title: 'Product 1',
description: 'This is product 1',
},
{
id: 2,
title: 'Product 2',
description: 'This is product 2',
},
{
id: 3,
title: 'Product 3',
description: 'This is product 3',
},
// ...
];
Then, let’s create the Products component that uses the render props pattern:
const Products = ({ render }) => {
// Fetching products can be done here.
// or use allProducts hardcoded data
const products = allProducts;
return render(products);
};
In the Products component, the ‘render’ prop is a function that renders the products. This function allows you to customize the rendering of the products when you use the Products component. It’s a vital part of the render props pattern, demonstrating how a component can delegate control over its rendering to a parent component.
For example, you can use the Products component in two different locations in your application and display the products in different ways:
const HomePage = () => {
return (
Products List
(
{products?.map(product => (
- {product.title}
))}
)}
/>
);
};
export default HomePage;
const ProductsSection = () => {
return (
Products List
(
{products?.map(product => (
{product.title}
{product.description}
))}
)}
/>
);
};
export default ProductsSection;
In this example, the first use of the Products component displays the products as a list of titles, while the second use displays their titles and descriptions.
This approach allows you to reuse the Products component and customize its rendering according to your needs in different application parts.
Some popular libraries that use the Render Props pattern include React router
, Formik
, and Downshift
.
Conclusion
The Render Props pattern in React is a powerful, flexible design approach that enhances component reusability and composability.
By passing a function as a prop to a component, developers can delegate control over the rendering logic to the parent component, enabling a more dynamic and customizable behavior.