The Future of React: Enhancing Components through Composition Pattern
Ricardo Silva
Posted on July 10, 2023
Introduction
Hello Fellow Developers! 👋
I can hardly contain my enthusiasm as I've been exploring into a revolutionary approach for structuring my components - the Composition Pattern in React! This strategy is a game-changer, elevating reusability, readability, and maintainability of the code to new heights. It provides a stunning advantage by enabling the addition or modification of functionality without ramping up complexity.
As a programmer, my mission goes beyond writing code. It's about creating software that simplifies people's lives, all while ensuring that this software remains scalable and a breeze to work with. I'm confident that this methodology can massively contribute to achieving this mission.
Fasten your seatbelts folks and get ready, we're blasting off into the future of code! 🚀
The Traditional Approach
In "traditional component architecture", it is common to encounter the need for passing numerous props throughout the component tree to incorporate custom styles or behaviours.
When starting a new project, it seems straightforward to introduce a new prop to handle a new specific behaviour or style. However, through my coding experience, I have come to realise this can easily lead to a pitfall. This approach eventually presents challenges in tracking which props impact which components at the same time it adds lots of complexity to maintenance due to the introduction or modification of functionality over time.
Let's take a typical example of a Call to Action (CTA) button. I chose to adopt this component because I believe that many developers have been caught up in the past, underestimating its value due to the perception of it being a simple button with text and styles. Therefore, I consider it to be one of the most under appreciated components.😂
Let's build the CTA component based on the given design, which includes a text and an optional icon positioned on the right side.
interface CtaButtonProps extends React.ComponentProps<'button'> {
variant?: 'primary' | 'secondary';
size?: 'small' | 'large';
iconName?: string;
text: string;
}
export const CtaButton: React.FC<CtaButtonProps> = ({
variant = 'primary',
size = 'medium',
iconName,
text,
className,
...props
}) => {
const classes = `btn ${variant} ${size} ${className}`;
return (
<button className={classes} {...props}>
<span>{text}</span>
{iconName && <Icon name={iconName} size="10" />}
</button>
);
};
And here's how it can be utilized:
<CtaButton iconName="+" text="Add more" />
I agree, this CTA appear straightforward, but I've learned never to put my complete faith in the designer. 🤣
Following a week, the requirements for the CTA changed. Now, it needs to be capable of displaying an icon on both the left and right sides, with the option to show them together or independently. Furthermore, the colour and size of each icon should be customisable based on where the CTA will be placed within the application.
Let's implement these modifications and observe the result:
interface CtaButtonProps extends React.ComponentProps<'button'> {
variant?: 'primary' | 'secondary';
size?: 'small' | 'large';
iconNameLeft?: string;
iconNameRight?: string;
iconColorLeft?: string;
iconColorRight?: string;
iconSizeLeft?: number;
iconSizeRight?: number;
text: string;
}
export const CtaButton: React.FC<CtaButtonProps> = ({
variant = 'primary',
size = 'medium',
iconNameLeft,
iconNameRight,
iconColorLeft,
iconColorRight,
iconSizeLeft,
iconSizeRight,
text,
className,
...props
}) => {
const classes = `btn ${variant} ${size} ${className}`;
return (
<button className={classes} {...props}>
{iconNameLeft && (
<Icon name={iconNameLeft} size={iconSizeLeft} color={iconColorLeft} />
)}
<span>{text}</span>
{iconNameRight && (
<Icon
name={iconNameRight}
size={iconSizeRight}
color={iconColorRight}
/>
)}
</button>
);
};
How it would be used:
<CtaButton
iconNameLeft="+"
iconColorLeft="red"
iconNameRight="+"
iconColorRight="green"
iconSizeLeft={20}
iconSizeRight={30}
text="Add more"
/>
And here we have the outcome.
We went from having 4 props to now having 9 props. Although it is relatively simple to comprehend which prop affects what in this case, it's important to remember that this is merely a basic example illustrating how minor changes in requirements can easily derail things.
The Composition Pattern
The Composition Pattern comes to the rescue to simplify these concerns.
Transition to Composition
Now, let's transform the previous example using the Composition Pattern:
interface CtaRootProps extends React.ComponentProps<'button'> {
variant?: 'primary' | 'secondary';
size?: 'small' | 'large';
}
export const CtaRoot: React.FC<CtaRootProps> = ({
variant = 'primary',
size = 'medium',
className,
children,
...props
}) => {
const classes = btn </span><span class="p">${</span><span class="nx">variant</span><span class="p">}</span><span class="s2"> </span><span class="p">${</span><span class="nx">size</span><span class="p">}</span><span class="s2"> </span><span class="p">${</span><span class="nx">className</span><span class="p">}</span><span class="s2">
;
return (
<button className={classes} {...props}>
{children}
</button>
);
};
export const CtaIcon: React.FC<React.ComponentProps<typeof Icon>> = (props) => {
return <Icon {...props} />;
};
export const CtaText: React.FC<React.ComponentProps<'span'>> = ({
children,
...props
}) => {
return <span {...props}>{children}</span>;
};
export const Cta = {
Root: CtaRoot,
Icon: CtaIcon,
Text: CtaText,
};
Understanding the Transition
In the above example, we've taken our single, prop-heavy CtaButton component and broken it down into three smaller, more manageable components: CtaRoot, CtaIcon, and CtaText. This is the core principle of the Composition Pattern - breaking components down into smaller, reusable parts that can be composed together to form more complex UIs.
Usage of Composition
Looking to the previous change of requirements to the Cta, with the Composition Pattern, we can effortlessly configure a CTA with an icon on the left, right, or none at all, not having to add any other logical complexity or a prop hell, instead we compose elements to achieve our desired UI.
Each icon can now be unique, with a different size, color, and more, because each element is exposed. 🤩
{/* Icon Left */}
<Cta.Root>
<Cta.Icon name="+" color="red" size={20} />
<Cta.Text>Add more</Cta.Text>
</Cta.Root>
{/* Icon Right */}
<Cta.Root>
<Cta.Text>Add more</Cta.Text>
<Cta.Icon name="+" color="green" size={20} />
</Cta.Root>
{/* Icon Both sides with different colors and sizes */}
<Cta.Root>
<Cta.Icon name="+" color="red" size={20} />
<Cta.Text>Add more</Cta.Text>
<Cta.Icon name="+" color="green" size={30} />
</Cta.Root>
Advantages of Composition Pattern
Embracing the Composition Pattern comes with several key benefits:
Reduced Prop-Drilling
Exporting components individually allows each sub-component to maintain its own props, eliminating the need to pass props down multiple levels.Explicit Control
Each sub-component can be included or excluded explicitly in the parent component. This eliminates the need for internal conditional logic to apply the different styles or behaviours.Easier Variants
If new design requirements emerge, such as having an icon on the left instead of the right, we can easily rearrange our composed sub-components without the need for additional props or logic.Clear Structure
The composition approach provides a clear structure of the expected child components and their relationship to the parent, making it easier to understand what a component's structure is at a glance.
Conclusion
Incorporating the Composition Pattern can significantly assist in maintaining a clean, modifiable, and expandable codebase. I view this as a considerable advancement in the strategy of crafting components and felt compelled to share this with you all.
I'm excited to hear your insights on this!
Let's revolutionise the way we craft React components together.
Posted on July 10, 2023
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.