Using the Open Closed Principle (OCP) in React

Using the Open Closed Principle (OCP) in React

A Comparison of Approaches with Code Example

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:

  1. Reduced Risk of Bugs: By not modifying the existing code, the risk of introducing new bugs in already tested code is reduced.

  2. 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.

  3. 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 and IconButton 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:

  1. 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.

  2. 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:

  1. 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 the Button component's code.

  2. 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.

  3. 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!

Did you find this article valuable?

Support Adeesh Sharma by becoming a sponsor. Any amount is appreciated!