The evolution of a React application

Juntao Qiu
ITNEXT
Published in
8 min readFeb 7, 2023

--

While I’ve put React application, there isn’t such a thing as React application. I mean, there are front-end applications written in JavaScript or TypeScript that happen to use React as their views. However, I think it’s not fair to call them React applications, just as we wouldn’t call a Java EE application JSP application.

Update 28 Feb. I published a video version just in case you prefer another format. Please subscribe to my channel for more related topics.

More often than not, people squeeze different things into React components or hooks to make the application work. This type of less-organised structure isn’t a problem if the application is small or mostly without much business logic. However, as more business logic shifted to front-end in many cases, this everything-in-component shows problems. To be more specific, the effort of understanding such type of code is relatively high, as well as the increased risk to code modification.

In this article, I would like to discuss a few patterns and techniques you can use to reshape your “React application” into a regular one, and only with React as its view (you can even swap these views into another view library without too many efforts).

The critical point here is that you should analyse what role each part of the code plays within an application (even on the surface, they might be packed in the same file). Separate view from no-view logic, split the no-view logic further by their responsibilities and place them in the right places.

This separation allows you to make changes in the underlying domain logic without worrying too much about the surface views, or vice versa. Also, it can increase the reusability of the domain logic in other places as they are not coupled to any other parts.

React is a humble library for building views

It’s easy to forget that at its core, React is a library (not a framework) that helps you build the user interface.

A JavaScript library for building user interfaces
— React Homepage

It may sound pretty straightforward. But I have seen many cases where people write the data fetching, reshaping logic right in the place where it’s consumed. For example, fetching data inside a React component, in the useEffect block right above the rendering, or performing data mapping/transforming once they got the response from the server side.

// ...

useEffect(() => {
fetch("https://address.service/api")
.then((res) => res.json())
.then((data) => {
const addresses = data.map((item) => ({
street: item.streetName,
address: item.streetAddress,
postcode: item.postCode,
}));

setAddresses(addresses);
});
}, []);

// the actual rendering...

Perhaps because there is yet to be a universal standard in the frontend world, or it’s just a bad programming habit. Frontend applications should not be treated too differently from regular software applications. In the frontend world, you still use separation of concerns in general to arrange the code structure. And all the proven useful design patterns still apply.

Welcome to the real-world React application

Most developers were impressed by React’s simplicity and the idea that a user interface can be expressed as a pure function to map data into the DOM. And to a certain extent, it IS.

But developers struggle when they need to send a network request to a backend or perform page navigation, as these side effects make the component less “pure”. And once you consider these different states (either global state or local state), things quickly get complicated, and the dark side of the user interface emerges.

Apart from the user interface

React itself doesn’t care much about where to put calculations or business logic, which is fair as it’s only a library for building user interfaces. And beyond that view layer, a frontend application also has other parts. To make the application work, you will need a router, local storage, cache at different levels, network requests, 3rd-party integrations, 3rd-party login, security, logging, performance tuning, etc.

With all this extra context, trying to squeeze everything into React components or hooks is generally not a good idea. The reason is mixing concepts in one place generally leads to more confusion. At first, the component sets up some network request for order status, and then there is some logic to trim off leading space from a string and then navigate somewhere else. The reader must constantly reset their logic flow and jump back and forth from different levels of details.

Packing all the code into components may work in small applications like a Todo or one-form application. Still, the efforts to understand such application will be significant once it reaches a certain level. Not to mention adding new features or fixing existing defects.

If we could separate different concerns into files or folders with structures, the mental load required to understand the application would be significantly reduced. And you only have to focus on one thing at a time. Luckily, there are already some well-proven patterns back to the pre-web time. These design principles and patterns are explored and discussed well to solve the common user interface problems — but in the desktop GUI application context.

In this article, Martin Fowler has a great summary of the concept of view-model-data layering.

On the whole I’ve found this to be an effective form of modularization for many applications and one that I regularly use and encourage. It’s biggest advantage is that it allows me to increase my focus by allowing me to think about the three topics (i.e., view, model, data) relatively independently.

Layered architectures have been used to cope with the challenges in large GUI applications, and certainly we can use these established patterns of front-end organization in our “React applications”.

The evolution of a React application

For small or one-off projects, you might find that all logic is just written inside React components. You may see one or only a few components in total. The code looks like HTML, with only some variable or state used to make the page “dynamic”. Some might send requests to fetch data on useEffect after the components render.

As the application grows, and more and more code are added to codebase. Without a proper way to organise them, soon the codebase will turn into an unmaintainable state, meaning that even adding small features can be time-consuming as developers need more time to read the code.

So I’ll list a few steps to help relieve the maintainable problem. It generally requires a bit more effort, but it will pay off to have the structure in you application. Let’s quickly review these steps to build front-end applications that scale.

Single Component Application

It can be called pretty much a Single Component Application:

Figure 1: Single Component Application

But soon, you realise one single component requires a lot of time just to read what is going on. For example, there is logic to iterate through a list and generate each item. Also, there is some logic for using 3rd-party components with only a few configuration code, apart from other logic.

Multiple Component Application

You decided to split the component into several components, with these structures reflecting what’s happening on the result HTML is a good idea, and it helps you to focus on one component at a time.

Figure 2: Multiple Component Application

And as your application grows, apart from the view, there are things like sending network requests, converting data into different shapes for the view to consume, and collecting data to send back to the server. And having this code inside components doesn’t feel right as they’re not really about user interfaces. Also, some components have too many internal states.

State management with hooks

It’s a better idea to split this logic into a separate place. Luckily in React, you can define your own hooks. This is a great way to share these state and the logic of whenever states change.

Figure 3: State management with hooks

That’s awesome! You have a bunch of elements extracted from your single-component application, and you have a few pure presentational components and some reusable hooks that make other components stateful. The only problem is that in hooks, apart from the side effect and state management, some logic doesn’t seem to belong to the state management but pure calculations.

Business models emerged

So you’ve started to become aware that extracting this logic into yet another place can bring you many benefits. For example, with that split, the logic can be cohesive and independent of any views. Then you extract a few domain objects.

These simple objects can handle data mapping (from one format to another), check nulls and use fallback values as required. Also, as the number of these domain objects grows, you need some inheritance or polymorphism to make things even cleaner. Thus you applied many design patterns you found helpful from other places into the front-end application here.

Figure 4: Business models

Layered frontend application

The application keeps evolving, and then you find some patterns emerge. A bunch of objects do not belong to any user interface and don’t care whether the underlying data is from remote service, local storage or cache. And then, you want to split them into different layers.

Figure 5: Layered frontend application

The above evolution process is a high-level overview, and you should have a taste of how you should structure your code or at least what the direction should be. However, there will be many details you need to consider before applying the theory in your application.

This is an excerpt of the long-form article I published on MartinFowler.com(thanks Martin for giving the article a great home, and please be aware that if you find any issues, that's my fault). The whole article is too long to fit into a medium blog. And I don’t want to break down it into too many parts, so have a read if you want to learn the concept within a thoughtful refactoring journey.

I hope you found this blog post informative and helpful. If you’re interested in diving deeper into the subject, I’ve created an online course that covers all the topics discussed in this post and more. This course is designed to be interactive, engaging and packed with practical tips and techniques.

If you like the reading, please Sign up for my mailing list. I share Clean Code and Refactoring techniques weekly via blogs, books and videos.

--

--