Abstraction and composition in React functional components

Published: 2021-10-01 | 3 min read

  • JavaScript
  • React

Abstraction is one of the most fundamental concepts in programming:

"the process of simplifying code by finding similarities between different parts of the code and extracting shared logic into a named component (such as a function, module, etc...), thereby reducing the amount of code required." (EricElliott)

By separating the code into independent units (i.e. functions or classes) you can reduce code duplication and increase reusability, while improving security and reliability of the application.

We use abstraction all the time in programming, whether by extracting frequently repeated operations into functions, or when importing internal or external modules and packages.

One of abstraction techniques is composition, which allows combining simpler data types into more complex ones (Wikipedia). This technique exists in functional- as well as object-oriented programming, and React's composition model is one of its fundamental blocks.

How to compose React components?

Imagine that you're building an app, where you will be often using a card to display information. The card will consist of three sections: heading, main and actions. While the heading and actions will be standardized across all cards, the main of a card might differ a lot between cards. You can achieve this through composition by using the special children prop, along with the custom props relating to title, subTitle, and actions with some default values.

const Card = ({ title, subTitle, children, actions = ['like', 'comment'] }) => (
<div class="card">
<div class="card-heading">
<h2>{title}</h2>
<p>{subTitle}</p>
</div>
<div class="card-main">{children}</div>
<div class="card-actions">
{actions.map((action) => (
<button>{action}</button>
))}
</div>
</div>
);

With the Card component defined, you can already apply composition when using it in another component to display a list of items. The entire ul element will be passed to the Card component in the children prop.

const App = () => (
<div className="App">
<h1>React composition</h1>
<div class="cards">
<Card title="Craft beer" subTitle="Most popular styles">
<ul>
<li>IPA</li>
<li>Sour</li>
<li>Stout</li>
</ul>
</Card>
</div>
</div>
);

If you're not planning to use lists inside the main section of a Card in another places within your app, this is a perfectly valid approach. However, you're planning to have a lot of cards that will include a photo below the heading. In this case, you can again use composition in order to make the Card component more specialized and turn it into PhotoCard.

const PhotoCard = ({ src, alt, children, ...rest }) => (
<Card {...rest}>
<img src={src} alt={alt} />
{children}
</Card>
);

PhotoCard introduces two new props, src and alt, which are necessary in order to display the photo. You can still make this new component further customisable by allowing children to be passed. What is important to remember, is that you still need to capture the props from the Card component, such as title, subTitle and actions. You can do that efficiently by collecting them with a ...rest operator and then spreading them into the Card component inside the PhotoCard component.

<PhotoCard
title="NEIPA"
subTitle="New England India Pale Ale"
src="https://www.beercartel.com.au/product_images/uploaded_images/neipa2017-cp.jpg"
alt="NEIPA photo"
>

<p>New England IPAs are beers that are purposely hazy or cloudy.</p>
</PhotoCard>

You can follow the same logic when creating another specialized variation of the card that you might use frequently, i.e. TextCard, which receives paragraphs, an array of strings as a prop. In this case, you can decide to no allow any further elements to be passed via children prop.

const TextCard = ({ paragraphs, ...rest }) => (
<Card {...rest}>
{paragraphs.map((paragraph) => (
<p>{paragraph}</p>
))}
</Card>
);

You can now use the TextCard along with other Card variations within your app. By passing actions to the TextCard, you can also override the default values specified in the Card component.

const App = () => {
const ipaInfo = [
'India pale ale (IPA) is a hoppy beer style within the broader category of pale ale.',
'The style of pale ale which became known as India pale ale was widespread in England by 1815, and would grow in popularity, notably as an export beer shipped to India and elsewhere.',
];

return (
<div className="App">
<h1>React composition</h1>
<div class="cards">
// ...
<TextCard
title="IPA"
subTitle="India Pale Ale"
paragraphs={ipaInfo}
actions={['save']}
/>

</div>
</div>
);
};

React composition example

You can find the code from these examples in thisĀ GitHub repository.

Conclusion

Composition is a very fundamental and powerful concept in React, as it makes it easier to manage your codebase by avoiding repetition, while providing flexibility in situations where you don't know what you might want to render inside a component ahead of time, or if you want to customize a component into its more specialized variation.