Advance React Patterns: Render Props
Tags: react, javascript, webdev, tutorial
Published: 2022-05-22 14:13:50

Hi 👋

If you have been working with React for quite some time, you must be well versed in writing reusable components. *Reusable UI components!* But as the codebase scales you often end up in situations where you want to share the business logic but UI may be different. 

💡 Situations like these are a perfect opportunity to use some advanced patterns. **Render Props** is one such pattern.

🚀 Some libraries that use render props pattern include React Router, Downshift and Formik.

Let's start with an example.

For instance, you are building an online store that showcases products. 

There is some common business logic that is required by every product list:
0. ✈️ Navigate to a page product page when clicked
1. 🤑 Sorting products based on price
2. 💾 Saving products
  - save in local storage if user is not logged in
  - otherwise store it in the DB using an API call

🎨 But based on where the list is being rendered UI can be different too. At one place you want to show stats, or product image, at other place you just want to show the title.

🧠 Let's understand the basic anatomy of a render props component, first.

```react

const Wrapper = ({ products, render }) => {

  // do some stuff
  const updatedProducts = someOperations(products)

  // provide some helper funcs for data like sort func
  const sort = () => {}

  return render({ updatedProducts, sort })
}


```

👉 Render Props components are just wrappers around your UI components. The component above gets two props - `products` and `render`. `products` is the data that needs to be modified using business logic and `render` is a function where this modified data and some other helper functions are passed.

🤔 But how do I use this component?

```react
// import everything

const HomeScreenProducts = () => {

  // assume you have this hook
  const { products } = useData()

  return (
    <ProductsWrapper 
     products={products}
     render={
       ({ updatedProducts, sort }) => updatedProducts.map(product => <ProductCard />)
     }
    />
  )

}

```

👉 Our `HomeScreenProducts` component uses `ProductsWrapper` and lets it handle all the business logic. UI is still controlled by the calling component.
Inside `render` function we consume the modified products data and render UI based on it.

😰 This looks a bit complicated. But we can simplify it to have a much cleaner API. Instead of passing a `render` function separately we can use the `children` prop.

After updating both the components look like this.

```react

const Wrapper = ({ products, children }) => {

  // same stuff here

  return children({ updatedProducts, sort })
}

```

```js

const HomeScreenProducts = () => {

  // same stuff here

  return (
    <ProductsWrapper products={products}>
      {({ updatedProducts, sort }) => updatedProducts.map(product => <ProductCard />}
    </ProductsWrapper>
  )

}

```

👌 This is much better. The `children` prop is serving the same purpose as the `render` prop we had earlier. This way of writing render props is more common.

⚠️ Don't forget to add `key` to your list.

💪 Now that we understand render props pattern, we can complete our task mentioned earlier.

```react
import React from "react";
import { useAuth } from "./hooks/useAuth";

const ProductsWrapper = ({ products, children }) => {
  const { isLoggedIn } = useAuth();
  const [sortedProducts, setSortedProducts] = React.useState(products);

  const sort = (order) => {
    const reorderedProducts = [...products];

    reorderedProducts.sort((a, b) => {
      if (order === "desc") {
        return b.price > a.price;
      } else {
        return a.price > b.price;
      }
    });

    setSortedProducts(reorderedProducts);
  };

  const save = (productId) => {
    if (isLoggedIn) {
      // call API
      console.log("Saving in DB... ", productId);
    } else {
      // save to local storage
      console.log("Saving in local storage... ", productId);
    }
  };

  const navigate = () => {
    console.log("Navigating...");
  };

  return children({ sortedProducts, sort, save, navigate });
};

export default ProductsWrapper;

```

We extend our `ProductsWrapper` component and add all the required functionality to it. It calls `children` as function and passes the data and helpers functions.

```react
import ProductsWrapper from "./ProductsWrapper";

const products = [
  { id: 1, name: "Coffee", price: 2 },
  { id: 2, name: "Choclates", price: 3 },
  { id: 3, name: "Milk", price: 5 },
  { id: 4, name: "Eggs", price: 4 },
  { id: 5, name: "Bread", price: 1 }
];

export default function App() {
  return (
    <div className="App">
      <ProductsWrapper products={products}>
        {({ sortedProducts, sort, save, navigate }) => (
          <>
            <div className="flex">
              <button onClick={() => sort("desc")}>Price: High to Low</button>
              <button onClick={() => sort("asc")}>Price: Low to High</button>
            </div>

            {sortedProducts.map((product) => (
              <div className="product" key={product.id}>
                <span onClick={() => navigate(product.id)}>
                  {product.name} - ${product.price}
                </span>
                <button className="save-btn" onClick={() => save(product.id)}>
                  save
                </button>
              </div>
            ))}
          </>
        )}
      </ProductsWrapper>
    </div>
  );
}
```

✅ Our UI component uses `ProductsWrapper` and takes care of the UI. As you can see, we are free to modify the UI or create other UI components that look totally different from this one. Our business logic resides at one place. 

If you want to play around with the example, it's available on codesandbox: [https://codesandbox.io/s/render-props-example-6190fb](https://codesandbox.io/s/render-props-example-6190fb)

That's all folks! :wave:

🤙 If this helped you, consider sharing it and also connecting with me on [LinkedIn](https://linkedin.com/in/thesanjeevsharma) and [Twitter](https://twitter.com/thesnjvsharma).