React Design Pattern Part 3: Render Props

Md Sajjad Hosen Noyon

31 August, 2024

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.

  1. View side components of click and hover counters.
  2. The Counter component takes the render function as a prop.
  3. The calling side calls the Counter component with a render function.
1. View Side Components

ClickCounter Component

				
					const ClickCounter = ({ count, incrementCount }) => {
 return (
   <button type="button" onClick={incrementCount}>
     Clicked {count} times
   </button>
 );
}
				
			
  • This component displays the number of times the button clicks.
  • It receives two props: count (the current count) and incrementCount (a function to increment the count).

 

HoverCounter Component

				
					const HoverCounter = ({ count, incrementCount }) => {
 return (
   <h1 onMouseOver={incrementCount}>
     Hovered {count} times
   </h1>
 );
}
				
			
  • This component renders a heading that displays the number of times it has been hovered over.
  • It also receives thecount and incrementCount 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 the count state.
  • The render function is called and returned with the current count and the countHandler function, allowing any component to use this counter logic.
3. Calling Side

App Component

				
					const App = () => {
 return (
   <>
     <Counter
       render={(counter, incrementCount) => (
         <ClickCounter count={counter} incrementCount={incrementCount} />
       )}
     />

     <Counter
       render={(counter, incrementCount) => (
         <HoverCounter count={counter} incrementCount={incrementCount} />
       )}
     />
   </>
 );
};
				
			
  • The App component uses the Counter component twice with different render functions.
  • The first Counter component renders a ClickCounter, passing down the count and incrementCount as props.
  • The second counter component renders a HoverCounter, passing down the count and incrementCount 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 (
   <div>
     <h1>Products List</h1>
     <Products
       render={products => (
         <ul>
           {products?.map(product => (
             <li key={product.id}>{product.title}</li>
           ))}
         </ul>
       )}
     />
   </div>
 );
};

export default HomePage;

const ProductsSection = () => {
 return (
   <div>
     <h1>Products List</h1>
     <Products
       render={products => (
         <div>
           {products?.map(product => (
             <div key={product.id}>
               <h2>{product.title}</h2>
               <p>{product.description}</p>
             </div>
           ))}
         </div>
       )}
     />
   </div>
 );
};

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.

Reference
Md Sajjad Hosen Noyon

31 August, 2024