How to Apply Software Architecture Design Principles in React Applications? Part 2

ky lim
5 min readSep 27, 2020

Moving on from part 1 of this series, we shall now discuss how to apply the remaining software architecture design principles of SOLID to react applications. We have discussed the Single Responsibility principle, Separation of Concerns, Open Close principle so now we shall move on with Liskov’s Substitution Principle, Interface Segregation Principle, and Dependency Injection Principle.

Liskov’s Substitution Principle (L in SOLID)

This principle states that “objects in a program should be replaceable with instances of their subtypes without altering the correctness of that program.

Ideally, objects of a superclass shall be replaceable with objects of its subclasses without causing any bugs or breaking the application. This is very similar to the Design By Contract concept which means that the interaction between components is based on precisely defined specifications of the mutual obligations — contracts. These can come in the form of the # assert macro in C and C++ or @ directives or decorators in Java and Python.

Back to Liskov’s Substitution Principle, an example of when this principle is violated in React/JavaScript is shown below:

Code for Shape class

In the code above, we have defined a Parent class that takes in dimensions in the constructor.

Code for Circle and Square class that extends Shape class

Then, from the code snippet above, we have a Circle and Square child class that extends Shape. We also have a function that expects a dimension object with a width and height attribute from the shape’s getDimensions() method. However, getAreaOfShape(circle) will cause errors since the input into the constructor for Circle is just an object with an attribute radius. Thus, this overall violates the Liskov’s Substitution Principle since Circle cannot substitute Square as a subtype of the same supertype Shape.

This also shows why Design by Contract can come in handy and prevent such violations.

To simply fix this, make sure we input the correct arguments into the Shape constructor or have each subtype Circle and Square have their own getShapeArea() method to call that is specific to their own attributes. There is also Babel plugin like Babel Contracts that help with Design by Contract for Javascript. TypeScript is also a really good way to write JavaScript for modern applications to develop by contract.

Interface Segregation Principle (I in SOLID)

This principle simply states that components or classes should not depend on methods of parent that as a child component it does not need. Since Javascript does not really support interfaces but Typescript supports interfaces, a simple example in Typescript would be the following:

Code for interface Shape in typescript Shape.ts

For the code snippet of the Shape interface above, the class of Square and Circle implementing the interface would have to depend on all the methods listed but getCircleArea() and getRadius() are methods that Square does not need and similarly, getSquareArea() is a method that Circle does not need. The class diagram below shows how this relationship:

Fig. 1 Class diagram of the Circle.ts and Square.ts implementing the interface of Shape.ts

This overall violates the principle of Interface Segregation and it would be better if the individual methods needed by some or one of the child components are in their own class rather than in the interface. Thus, getRadius() should just be declared and implemented in Circle.ts and instead of the two methods to get area, getArea() can be declared in the interface and have different implementations in Square.ts and Circle.ts. The class diagram of this implementation is shown below:

Fig. 2 Class diagram of the Circle.ts and Square.ts implementing the new interface of Shape.ts

Dependency Inversion Principle (D in SOLID)

Dependency inversion is when the application depends on interface or abstractions rather than specific instances of classes or functions. To put it simply, component A defines the work to be done (defines the abstraction) and component B does the work. Component A depends on the work done by Component B but does not know how it is done (depends on the abstraction defined by Component A).

Dependency inversion comes in the form of the props being pass to other components in React. If we have a component AlertBox.js that takes in as props a message, other components do not need to know how the AlertBox.js message is rendered and the work to be done by AlertBox.js which is to render a specific message was defined by the components that used it. However, if you find yourself having to pass down props to many components transitively, it might be a good idea to use React Context or React-Redux. The example of the AlertBox.js code is shown below:

Code for AlertBox.js

Besides passing props into components to use the component with abstraction, high-level components also should not have to rely on low-level details. An example of how a high-level component Page depends on low-level details is shown below:

Code of the onSubmit() method that is used in a component called Page.js

The code snippet shows a onSubmit() method in the Page component. The Page component would depend on the global posts post method but it would be better if the Page component is more loosely coupled without having to know the method to post posts. This can be done with what is shown below:

Code for new Page.js

The code snippet shows how onSubmit() can be passed as props and this also allows to easily change the post fetching method without going into the Page component at all.

This blog concludes the series on how to apply software architecture design principles like SOLID in React. I am also aware that certain principles developed many years ago may not be as applicable to evolving and popular technologies today like React or certain languages like JavaScript, like the Interface Segregation Principle, but we just have to make the best decision based on the weighing the pros and cons of certain implementations with the issues of rigidity, fragility, and immobility in mind. I am sure there are more good practices when it comes to web application development with React. I hope some of the points shared were insightful and I would love to hear from you about any other examples or ways we can apply good software engineering design patterns or principles in React or even bad practices in React for everyone to take note of.

--

--

ky lim

Aspiring Software Developer who is currently in the last year of my Computer Science major at the National University of Singapore