Data Driven React UI Components

May 28, 23

Component structures are very unopinionated in terms of how they receive data - is it via props, or do they handle their own data/business logic? How your components are designed largely decide the fate of our application, onto how people are going to reuse the components, are they actually reusable or not, how much of a refactor does a component require before plugging in, how easier is it to plug the component and just use it.

Data Driven React UI Components - cover

Component structures are very unopinionated in terms of how they receive data - is it via props, or do they handle their own data/business logic? How your components are designed largely decide the fate of our application, onto how people are going to reuse the components, are they actually reusable or not, how much of a refactor does a component require before plugging in, how easier is it to plug the component and just use it. This blog solely focus on Data Driven Components - components which depend upon data from APIs and are not just UI components like buttons or list. I personally prefer to call these components widgets - (by definition - an application, or a component of an interface, that enables a user to perform a function or access a service.)

For this blog, let’s take an example for Product Cards from an e-commerce website that just simply shows the price, an image and the name of the product. Something similar to this 👇🏻

Product Card

Now let’s discuss about the various ways I can write this component.

Component Handling the API Calls

This is a very naive way of creating any data driven component - let the component handle the data calls. So for our example, the component will accept a prop for the product id and then makes an API call to pull the details.

const ProductCard = ({ productId }) => {
const data = useFetch(`/products/${productId}`)

return (
<div>
<img src={data.image} />
<h2>{data.title}</h2>
<h3>{data.price}</h3>
</div>
)
}

Pretty slick right? No, actually not ☹️, this type of a component may work fine, where there is only 1 product has to be shown on the page, but assume there is a list of products you wanna show? Something like this 👇🏻

Product Grid

In this case, you won’t like to make multiple API calls for all of the products, but rather you would write your components something like ⏭️

Data through Props

The usual way, how most of us have been writing components in React or with any UI Framework - by passing down the data required for UI rendering via props.

const ProductCard = ({ product }) => {
return (
<div>
<img src={product.image} />
<h2>{product.title}</h2>
<h3>{product.price}</h3>
</div>
)
}

const ProductList = () => {
const data = useFetch('/all-products')

return (
<div>
{data.map(product =>
<Product product={product} />
)}
</div>
)
}

Perfect 😃, the good part with this is that at least the ProductCard component is reusable 🎉 but hold on, now you see the problem, this so called widget - ProductList is kind of coupled with the /all-products API, what if I wanted to use this list component for recommended products as well or maybe on the search page as well?

So to fix this issue, we can also pass the API endpoint via a prop and handle the returned data, like this →

const ProductList = ({ api }) => {
const data = useFetch(api)

return (
<div>
{data.map(product =>
<Product product={product} />
)}
</div>
)
}

const Page = () => {
return (
<div>
<ProductList api="/all-products" />
<ProductList api="/recommended-products" />
<ProductList api="/search-products" />
</div>
)
}

Which is good, but this is only possible when we are assuming that all of these APIs are gonna return the data is same shape.

And also does this way of writing ProductList component makes it harder to test - no matter integration test or e2e test, you would have to pass a mock service API to the prop and let it make fetch calls in test environments, which I believe is not required for testing React UI Components.

Data Agnostic Components

I believe a much better solution to the above problem with a very standardised format of writing your React code is to have all components data agnostic - not worrying about how data is fetched, where is it coming from. Any component should have one and only one job - render the UI based on whatever props are there. So now our whole page will look like this -

const ProductCard = ({ product }) => {
return (
<div>
<img src={product.image} />
<h2>{product.title}</h2>
<h3>{product.price}</h3>
</div>
)
}

const ProductList = ({ products }) => {
return (
<div>
{products.map(product =>
<Product product={product} />
)}
</div>
)
}

const Page = () => {
const allProducts = useFetch('/all-products');
const recommendedProducts = useFetch('/recommended-products');
const searchProducts = useFetch('/search-products');

return (
<div>
<ProductList products={allProducts} />
<ProductList products={recommendedProducts} />
<ProductList products={searchProducts} />
</div>
)
}

The best part about this way of writing your React components is that they are Pure functions - will always render the same UI given the same props, they are easier to test, and most of all they are reusable.

The only problem I encounter while writing react components this way is that having the data layer at pages level components brings a cumbersome job of maintaining the loading and error states for these APIs. So the solution to that is as usual passing the loading and error states as props to the component, or we can also check the actual data props itself - if it is undefined → we can assume it is loading, if it is of type error then definitely an error or else the usual UI. Something like

const ProductList = ({ products }) => {
if (!products) return <div>Loading...</div>

if (products.error) return <div>Something went wrong...</div>

return (
<div>
{products.map(product =>
<Product product={product} />
)}
</div>
)
}

Conclusion

Let me conclude this whole confusion by stating my opinion - the way I write my application code:

In layman chemistry 🧪 terms, components can be referred to as atoms ⚛️, widgets can be termed as molecules while your pages can be as your tissues/organs 🫁