Building SOLID React Apps

Building SOLID React Apps

·

4 min read

Introduction

SOLID is a set of object-oriented design principles. These principles establish practices for developing classes in object-oriented programming.

The SOLID principles aims to serve one purpose which is to create reusable, maintainable, flexible and easy-to-understand code in order to facilitate collaborative software development.

Despite being created for object-oriented programming, the principles of SOLID can be applied in React applications, although they may manifest slightly differently due to the nature of React being a library for building user interfaces rather than a strict object-oriented programming paradigm. In this article, you would see how each SOLID principle can be applied in the context of React.

S - Single Responsibility Principle (SRP):

Components should have a single responsibility. Separate concerns such as rendering, state management, and data fetching into different components or hooks to keep each component focused and maintainable.

For example, a Product Card component on an eCommerce website responsible for rendering product information (name, image, description) should only handle rendering the product and not perform unrelated tasks such as fetching data or component actions (e.g. on button click). Container components which sit above presentational components like ProductCard should handle data fetching, application logic, and pass down the data as props to presentational components.

From the image below, the code at the top, violates SRP because it has two concerns in the component code:

  • Displaying product details

  • Handling cart interactions (adding the product and managing errors).

The corrected code shows separated functionalities:

  • The ProductCard now renders only product information (presentational information).

  • The parent/container component handles interactions and other actions related to the children components.

O - Open/Closed Principle (OCP):

You should be able to extend the behavior of a component without needing to directly modify its source code. Use higher-order components (HOCs), composition or props to customize and extend the behavior of components rather than modifying their internals directly. This is closely related to the first principle.

Still using the ProductCard component, the first part of the image shows the component which violates the OCP principle, this makes it hard to reuse the ProductCard across the application because it now has a local handleAddCart function which cannot be customized.

L - Liskov Substitution Principle (LSP):

This principle emphasizes that objects (components in this case) of a superclass should be replaceable with objects of its subclasses without causing unexpected behavior. Create reusable components with well-defined props that can be easily substituted for one another in different parts of the application.

An example of this is a basic Button component that renders a button with a label and onClick handler. You then create a derived component PrimaryButton that inherits from Button and adds styles for a primary style button.

The first image which violates LSP, shows how the PrimaryButton directly modifies the props passed to the Button component - adding a style attribute. If another component expects a plain Button without styles and uses PrimaryButton as a replacement, it might break the UI due to unexpected styling.

I - Interface Segregation Principle (ISP):

The I in SOLID stands for the Interface Segregation Principle. This principle emphasizes that instead of having large, monolithic interfaces(props), it's better to break them down into smaller, more specific interfaces. This allows for greater flexibility and reduces the burden on classes (components) that implement these interfaces.

React heavily utilizes components, and interfaces(props) define the way components can interact with each other. ISP emphasizes that avoid passing large prop objects to components. Instead, break down props into smaller, more specific ones that components can use independently. The example below shows how the Interface Segregation Principle can be applied in React:

A ProductCard component that displays product information and allows users to add the product to their cart. Initially, you define a single interface, ProductProps, that includes all functionalities:

This interface serves the ProductCard component well. However, what if you want to reuse the product information display functionality (name, price, image) in another component, say a ProductSearchList component that displays a list of products? In this case, the ProductSearchList component doesn't need the addToCart functionality.

Following the Interface Segregation Principle, you can split the original ProductProps interface into two smaller ones:

Now, the ProductCard component can implement both ProductInfoProps and AddToCartProps, while the ProductSearchList component only needs to implement ProductInfoProps. This promotes better code reusability and reduces the dependencies between components.

Consider using ISP for components with multiple, distinct functionalities and potential for future growth.

D - Dependency Inversion Principle (DIP):

Avoid tight coupling between components by using dependency injection, context, or props drilling to pass dependencies down the component tree. Favor abstractions and interfaces over concrete implementations to decouple components and make them easier to test and maintain.

An example of how the DIP in React is this: Imagine you have a ProductCard component that fetches product data from a hardcoded API endpoint within the component itself. This creates tight coupling between the component and the specific data source.

Summary

While React may not be strictly object-oriented, the principles of SOLID can still guide you in writing cleaner, more maintainable React applications by encouraging modular, decoupled, and reusable component designs.

Though originally intended for object-oriented programming, the core concepts translate well to React's component-based structure.