After several professional experiences with multicultural teams, it is easy to identify patterns that drive us to the User Interface, which is hard to read and understand. Usually, we don’t seek or want poor composition in React, but we can find ourselves in a code jungle after several months of maintenance.
It becomes challenging to identify the time when our components represent an issue in the long term. However, there are several main reasons or signals that we should be aware of. Commonly, we can see anti-patterns being applied to our codebase, handling too much business logic, too many state variables, or a large file size.
From a technical perspective, we lose all the advantages of using component-based UI frameworks, as we do not implement small, reusable components; instead, we leave all the responsibility to a few components, increasing the complexity for our entire codebase while localizing the reason for a specific issue.
The probability of finding a tight relationship between components is high, so even working with other engineers becomes complex, as reading and working with long components usually brings some headaches to the table.
As part of the main features for an Ensolvers’ customer, we implemented an email template composition functionality. This component grows too fast and not in a proper way, as we have had to modify this code several times in a short period.
The main component has complete control over three tabs inside the modal. The first tab contains an extensive collection of inputs that represent a form to compose the email template using AI. The second tab manages a complete list of entities to select the proper email template for the task. The last tab had an interface that allowed the user to write HTML documents to feed our LLMs.
We found several issues in our component tree composition. Mainly, we see an extensive list of state variables, constant values, and business logic. We found a high level of cohesion among the components’ logic functions, which made refactoring a time-consuming and complex task.
The first thing we notice when we open this component is that it has a lot of static content, which is not required for the element itself. We should be aware of the domain that we are working with. The primary purpose of a .tsx file is to handle both state variables and visual pieces.
Then we find complex business logic, which is tied to the state variables. This is not a problem per se, but in large files, this is a way to optimize the code size. Another issue is that many state variables are being shared in a single place. Another remarkable issue is that we find some side effects for the polling mechanism, which is not a significant issue, but should be addressed to fix the content and our composition tree.
In UI, it’s common to see a mix of different responsibilities. However, this is not mandatory. We can apply principles as the separation of concerns. Using React, we can separate state management, business logic, and visual styling.
Our main goal was to isolate different parts of the code and break them down into smaller, well-defined logical pieces with clear responsibilities.
Our first move is to change all the constant values in their file. The main component does not require knowing the definition of the continuous values, just the values to hydrate the UI so that this content can be safely placed here.
Then we should handle the business logic to avoid declaring it in the component itself, and avoid saying the significant functions when possible. Not because of performance or something, but it’s necessary to increase readability.
This action can be achieved in two steps. The first of them is separating the state-managed variable from action handlers, which do not depend on the state itself. The second step is moving the functionality to a file outside of ours, which can run our action without dependency on React components.
Let’s say this function, calculateEmailBasicInfo, has a complex structure and is difficult to understand.
The value should depend only on the parameters for the function and be called using the state variable.
Now our code is completely independent of the context and state of the component, so it can be extracted to a single file, which collects all the email template generation functions and then just calls the function from components.
As the feature was heavy in processing time, we implemented a basic algorithm for polling the composition state for a single email template. This logic was helpful to us, but we had several implementations doing the same, so it was challenging to make a minor change in too many places when we pulled email composition states.
The solution for this case was implementing our custom hook, providing a single place to share the process of polling.
This solution can be extracted to his custom hook.
Now our custom hook can be called in all the components that need this behavior.
Composing our graphic interface more smartly allows us to have cleaner code, which is easy to predict behavior, easy to read, and easy to maintain, bringing professional and scalable solutions to any React project.