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

ky lim
6 min readSep 27, 2020

--

Having gone through various software engineering modules as a Computer Science student, I have been taught many software architecture design principles but now that I am working on a web application using React, how can I apply those principles?

That’s exactly what this blog shall be exploring. I am going to share how we can apply some software architecture design principles like SOLID, Separation of Concerns, Information hiding in applications made using a popular framework, React.

In this first part, we shall discuss how to apply the Single Responsibility Principle, Separation of Concerns, and the Open Close Principle.

Firstly, what purpose do these principles serve? These principles are helpful guidelines to guide us on how to overcome the issues of rigidity, fragility, and immobility which are not good for our software codebase. What does each issue mean?

Rigidity

Resistance to change. When one change causes a cascading effect for change on other components.

Fragility

Comes hand in hand with rigidity. When one change causes the software to break.

Immobility

Unable to re-use the components in the software

These issues make software difficult to manage, extend, improve, or change which is very undesirable when we have important software that strives to meet changing requirements or are in the works with unstable requirements. Even deployed applications may require changes or extension overtime. Now that we know why principles are important, let’s discuss more on how to apply it.

Separation of Concerns & Single Responsibility Principle (S in SOLID)

First, one module should have one and only one reason to change. This implicitly uses abstraction which is to hide unnecessary details from other components or users and just needing to know how to use the component.

Let’s say we are trying to build an application that on the homepage allows users to be added and notifications to be shown.

The App.js file contains the state of users array and notifications array which would be rendered on the App render() method. Then the App.js file also contains the methods to fetch, add and delete users from a database linked and to fetch and remove notifications from a database linked as well.

Below shows the render() method for App.js and its class diagram:

Code for the render() method in App.js
Fig. 1 Class diagram of the App.js file

This App.js file component has more than 1 reason to change and violates the Single Responsibility Principle. It also has various pieces of interests or focuses which are the Users section and notifications section which violates the Separation of Concerns principle.

Instead, we could split the Users section and notifications section from the App.js in UsersPanel.js and NotificationsPanel.js file, each having its own responsibility. Then, these 2 components can be rendered inside App.js.

Below shows the code for App.js, UsersPanel.js, and NotificationsPanel.js and its class diagram:

Code for App.js with child components UsersPanel.js and NotificationsPanel.js
Code outline for UsersPanel.js
Code outline for NotificationsPanel.js
Fig. 2 Class diagram of App.js that renders child components, UserPanel.js and NotificationsPanel.js

For this design principle, the piece of interest or focus may not just be files or components but classes, methods, subsystems, or implementation of the algorithm as well. Just keep in mind to make sure you do abstractions well. This can help overall improve the readability and manageability of the code, managing complexity and allow for parallel development within a software development team since each person can work on separate parts simultaneously without depending too much on each other.

Open Closed Principle (O in SOLID)

This principle states that “software entities (class, modules, functions) should be open for extension, but closed for modification.”

Open for extension

The module’s behavior can be extended

Closed for modification

Extending the module does not cause changes to the source, binary, or code of the module.

Let’s say for the UsersPanel.js, we have implemented it to fetch, add, delete active users using the methods shown in Fig. 3 below. Then it renders UsersList.js. We map the Users array to return User.js for each user which is passed in a user object as props for it to render in its own format. User.js can accommodate the rendering of online users because it renders the attributes of the name, age, email for each online user.

The code snippet below shows the render() method for UsersPanel.js and the User.js, as well as the objects, returned from the database for each user and the overall class diagram:

Code of the render() method for UsersPanel.js
Code of the render() method in User.js
Code of the User object with its attributes
Fig. 3 Class diagram of UsersPanel.js with OnlineUsersPanel.js rendered

However, let’s say we now want to implement another panel within the UsersPanel.js that fetches and deletes offline users as well as renders these offline users in the UsersPanel.js with its own formatting. Offline users now have extra attributes like lastOnlineTime and we want to implement a button to allow leaving messages to offline users. If we were to edit the User.js’s render method to check for the extra attributes then render the new attributes and implement the button for offline users this violates the Open Close Principle because we now have to modify the Users.js code to accommodate the extension of offline users which reduces readability and does not make use of re-usability.

An example of the new user object with its attribute is shown below:

Code of the OfflineUser with its attributes

Instead, we can have another base component called BaseUser.js that renders the User component inside and adds the extra rendering of the extension features in its render method. Then, we would not edit the original UsersList.js.

The BaseUser.js, the edited render() method for UsersPanel, and the overall class diagram is shown below:

Code of BaseUser.js
Code of the new render() method in UsersPanel.js
Fig. 4 Class diagram of UsersPanel.js that uses BaseUser.js to encapsulate User.js

Therefore, we have used a composition pattern to apply the Open Close Principle which helps improve the re-usability of our code and ensures minimal repetition of code. Since JavaScript does not support the classic inheritance like Java, functional composition is preferred over inheritance as shown above.

This concludes the first part of Applying Software Engineering Design Principles in React Applications series. Do keep a lookout for the second part of the series where other remaining SOLID principles would be discussed.

--

--

ky lim
ky lim

Written by ky lim

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

No responses yet