React Code Characteristics (part 2 of 2 of an Angular/React Comparison) by Julian Flaks

In a previous post, EQengineered discussed some of the maintenance realities one can encounter in looking after an Angular application. In this post, we want to compare and contrast some of the realities of the React ecosystem. As before, this is anecdotal, but based on a few situations EQengineered has come across.

Assuming a completely functional component codebase, a React app will likely have its own layout of code with components grouped by functional areas. Whether using Redux, some other global state solution, or the now included context and reducer patterns, one will hopefully find a repeated and isolated pattern of code for dealing with server calls, as well as for handling data. React allows many styles by virtue of its own minimal footprint and open-minded patterns; however, if global state has not been relied on, one may find arbitrary accumulations of business logic and data transformation within the components themselves.

Component Wake-ups / Re-render Opt Outs

In theory, the waking up of components in response to events and data flow is what React keeps simple, and with a well understood codebase, that is often evidenced. When people add code on top of touchy or incomprehensible code, one can sometimes find lifecycle fill-ins - redundant attempts to make a component respond at the right times, or to refresh when data caching or other mechanisms are not behaving as intended. Illogical code can be a magnet for more code since it takes a responsible developer to ignore or remove extraneous noise when making time-sensitive additions. As of React 18, the Suspense feature for deferred rendering addresses render efficiency from a different direction, and as a newer feature its implications for codebase complexity have yet to be encountered.

Prop Transformation

One of the drivers of global state is avoiding pass-through props in components; middle-men which have to send detailed props to descendants in their hierarchies even though they have no need or understanding of the data involved. If the prop mechanism is used in such a way, it is vital to avoid translating and transforming the props along their journey. Manipulation of props is a source of code complexity that should be easy to refactor, but which has the capability of embedding itself longer term just because different components are dealing with the data in different guises, and refactoring them to uniformity takes discouraging amounts of effort.

Old React vs New React

React had its own paradigm shift rather like the one which Angular went through, in the form of functional versus class components. The big difference was that React’s change was purely additive; though functional components represent very different code compared to class components, they are very close underneath and 100% compatible. It is not that unusual to find apps which have a combination of the two. This is not like a hybrid AngularJS/Angular UpgradeModule situation, but the meeting of class and functional components becomes a breeding ground of un-refactored code and therefore complexity. This is because React code maintenance requires the developers to move code up and down components as logic changes, and to move code between a class component and functional component necessitates more thorough rewriting. It is this sort of accumulation of un-refactored code that can help persuade teams it is time to make the transition fully to the new style.

Outliers - HOCs

Now an outlier in React, the use of Higher Order Components was an attractive mechanism for quickly sharing code. However, it was more of a mix-in approach versus inheritance, and tracing behavior through the source-code, let alone executing refactors, became harder as more HOCs added their own complexity.

Summary | Take Aways

Surveying the types of complexity and maintenance situations does not necessarily help decide a "winning" framework, but culturally may be a good clue as to what will and will not start to be annoying and counterproductive for particular engineering teams or applications.

In both cases, diligence with code maintenance and layout, DRY approaches and above all, using the framework along the patterns it is designed for, help to achieve a codebase which teams can still enjoy developing for years to come.

Julian Flaks