Using the Open Closed Principle (OCP) in React
React, a widely used JavaScript library for building user interfaces, is accustomed to using software design principles that make code easier to maintain and more readable.
The "Open-Closed Principle" (OCP), a key idea in the realm of object-oriented programming, is one such approach that has shown to be particularly helpful in this regard (OOP).
Although React isn't exactly OOP, you can still use many of these ideas, including OCP, to make your React code more resilient.
What is the Open-Closed Principle?
The Open-Closed Principle, first proposed by Bertrand Meyer, asserts that "software entities (classes, modules, functions, etc.) should be open for extension, but closed for modification."
Breaking it down, the term "open for extension" implies that the behavior of a module can be extended, allowing it to have additional capabilities.
The term "closed for modification" on the other hand denotes that after a module has been created and tested, its source code shouldn't be changed as this could result in the introduction of new issues.
OCP in the Context of React
While React is not an object-oriented framework, we can apply the OCP philosophy to component design. React components, like software entities in OOP, should be created in such a way that they can be reused and extended without the need to alter their source code.
Let's say we have a Button
component that is being used in multiple places in our application. Later on, we decide that we need a Button
with a slightly different behavior, say an Icon
with an icon alongside the text.
In a non-OCP-compliant scenario, one might be tempted to directly modify the existing Button
component to accommodate this new functionality. However, this violates the principle, as modifying the Button
component could potentially introduce bugs in all places where the component is used.
Instead, we can apply OCP by creating a new Button
component that extends the functionality to render any Icon that is passed down to it from its parent component where the Button
is being used, without modifying its source code. Later in this article, we will be comparing the approaches with the same example.
Relevance and Benefits of OCP in React Development
Adherence to the Open-Closed Principle provides several benefits:
Reduced Risk of Bugs: By not modifying the existing code, the risk of introducing new bugs in already tested code is reduced.
Enhanced Code Readability: Components designed with the OCP in mind are more modular, easier to read, and comprehend. Each component has a single responsibility, making them easier to understand individually.
Improved Code Reusability: Components become more reusable as they are designed to be extended, not modified. This results in a reduced amount of code as components can be reused with different configurations, as seen in our
Button
andIconButton
example.
Bad approach
OCP/index.tsx
import { Button } from "./Button";
const OCP = () => {
return (
<div className="flex space-x-10">
<Button text="Go Home" role="forward" />
<Button text="Go Back" role="back" />
</div>
);
}
export default OCP
OCP/Button.tsx
import {
HiOutlineArrowNarrowRight,
HiOutlineArrowNarrowLeft,
} from "react-icons/hi";
interface IButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
text: string;
role?: "back" | "forward" | "main" | "not-found";
icon?: React.ReactNode;
}
export const Button = (props: IButtonProps) => {
const { text, role } = props;
return (
<button
className="flex items-center font-bold outline-none pt-4 pb-4 pl-8 pr-8 rounded-xl bg-gray-200 text-black"
{...props}
>
{text}
<div className="m-2">
{role === "forward" && <HiOutlineArrowNarrowRight />}
{role === "back" && <HiOutlineArrowNarrowLeft />}
</div>
</button>
);
};
The provided code sample provides an interesting demonstration of a typical error that violates the Open-Closed Principle (OCP). Let's examine the code in more detail and highlight the drawbacks of this strategy.
The Button component is made to be flexible and capable of handling various jobs. It presents a different icon next to the button's text depending on the role provided ("back," "forward," "main," or "not-found"). A prop supplied to the component determines the role.
Although this may seem like a smart, reusable solution at first glance, it has a serious downside: it violates the OCP. Here's why:
Potential for Modification: As the application grows and new button types are required, the
Button
component may need to be constantly modified to handle new roles. For example, if we need a "refresh" button with a refresh icon, we would have to add another condition in the code. This continuous modification could potentially introduce bugs.Increasing Complexity: The more roles we add, the more complex our component becomes, making it harder to maintain and understand. It contradicts the simplicity and single-responsibility best practices in software design.
Good Approach
OCP/index.tsx
import { Button } from "./Button";
import {
HiOutlineArrowNarrowRight,
HiOutlineArrowNarrowLeft,
} from "react-icons/hi";
const OCP = () => {
return (
<div className="flex space-x-10">
<Button text="Go Home" icon={<HiOutlineArrowNarrowRight />} />
<Button text="Go Back" icon={<HiOutlineArrowNarrowLeft />} />
</div>
);
};
export default OCP;
OCP/Button.tsx
interface IButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
text: string;
role?: "back" | "forward" | "main" | "not-found";
icon?: React.ReactNode;
}
export const Button = (props: IButtonProps) => {
const { text, icon } = props;
return (
<button
className="flex items-center font-bold outline-none pt-4 pb-4 pl-8 pr-8 rounded-xl bg-gray-200 text-black"
{...props}
>
{text}
<div className="m-2">{icon}</div>
</button>
);
};
The code snippets provided are good examples of following the Open-Closed Principle (OCP) in a React application. This time, the Button
component is made to be adaptable and extendable without needing to modify its internal logic. Let's dissect this code to understand why it's an excellent example of adhering to the OCP.
The Button
component receives text
and icon
as props, which define the label and the icon of the button, respectively. Here, the Button
doesn't have to deal with multiple roles and decide what icon to display based on those roles.
In the OCP
component, different icons are passed to the Button
component for different instances, giving it the required behavior.
This approach is better for a few reasons:
Modularity and Reusability: The
Button
component can be used in any scenario, with any text or icon, without needing to understand or handle specific roles. You're free to use any icon library or custom icons without altering theButton
component's code.Easier Maintenance and Extension: If you need a new button with a different icon or text, there's no need to modify the existing
Button
component. You just extend its use by passing the new icon or text as props.Decreased Complexity: By separating the concerns (button display vs. button behavior), the code is easier to understand and manage.
This approach aligns perfectly with the Open-Closed Principle — the Button
component is closed for modifications (we don't need to change its code to add new features) and open for extension (we can give it new behavior by passing different props). Therefore, by following the OCP, the code becomes more robust, flexible, and maintainable.
Try it out on Code Sandbox
https://codesandbox.io/p/sandbox/react-ocp-6fjv4h
Conclusion
In conclusion, applying principles like the Open-Closed Principle (OCP) in React, although it is not an Object-Oriented framework, can significantly improve your code. The OCP paves the way for more robust, maintainable, and extendable components.
By embracing the spirit of OCP, our Button
component becomes a prime example of code that is closed for modification yet open for extension, thereby reducing bugs, enhancing readability, and improving reusability.
Remember, while these principles serve as excellent guidelines, they aren't hard and fast rules. Use them wisely to shape a codebase that not only works but also stands the test of time. Happy React coding!
Subscribe to my newsletter
Read articles from Adeesh Sharma directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
Adeesh Sharma
Adeesh Sharma
Adeesh is an Associate Architect in Software Development and a post graduate from BITS PILANI, with a B.E. in Computer Science from Osmania University. Adeesh is passionate about web and software development and strive to contribute to technical product growth and decentralized communities. Adeesh is a strategic, diligent, and disciplined individual with a strong work ethic and focus on maintainable and scalable software.