The Messy Middle — Navigating Complexity in Large React Applications

Rate this content
Bookmark

The project started out great—the team was motivated, moving fast, and shipping features ahead of schedule. But little by little, complexity found its way in. Implementing small changes takes forever now, tech debt is piling up at an alarming rate, and everyone is losing confidence that the project will ever get done. Welcome to the messy middle. 


Taking examples from real-world projects that shipped way too late, we'll explore the symptoms and causes of complexity in large React applications, and we'll share tips and strategies for dealing with it before it takes over your codebase.

10 min
15 Nov, 2023

Video Summary and Transcription

Managing complexity in large React applications is a challenge, as it tends to grow exponentially over time. Component composition is a key technique that allows for flexibility in React applications, but too much composition can make components harder to use. Managing complexity and reducing cognitive load is crucial, and strategies such as controlling abstraction evolution and minimizing information needed for simple changes can help. Simplicity is difficult, but important in slowing down the growth of complexity.

Available in Español

1. Managing Complexity in Large React Applications

Short description:

Hi React Summit, my name is Maxi Ferreira and today I want to share with you some of my thoughts on managing complexity in large React applications. We often imagine complexity as growing linearly over time, but it tends to grow exponentially. I will define complexity as anything that makes the system harder to change or understand. I will share my favorite three strategies and techniques to help you manage this growing complexity in large React projects.

Hi React Summit, my name is Maxi Ferreira and today I want to share with you some of my thoughts on one of my favorite topics, which is managing complexity in large React applications. But before we start, let's picture the journey of any large project. We might imagine that it will go something like this, we're going to make steady progress over time until we finally reach the finish line, we ship to production and we go out to celebrate with our team.

In reality, though, it's much more likely that it will go something like this. Things get messy in the middle. Features now take forever to ship, tech dev accumulates very rapidly and the finish line seems to get farther away each day. The reason this happens is that we often imagine complexity as growing linearly over time. The more progress we make, the more complex the codebase grows, and we sort of expect that. But more often than not, complexity tends to grow exponentially. So when we're only halfway through the project, we can see that complexity is not slowing down. It can feel quite overwhelming.

For the purpose of this talk, we're going to define complexity as anything that makes the system harder to change or harder to understand. What I like about this definition is that we can flip it and we get exactly what we need to do to achieve simplicity, which is making things easy to change and easier to understand. So today I want to share with you my favorite three strategies and techniques to help you manage this growing complexity in large React projects, which I'm hoping will be useful, especially if you're in the messy middle of your project right now.

2. Component Composition in React

Short description:

Component composition allows for flexibility in React applications. By breaking down a large component into smaller ones, we can make changes without adding numerous props. However, too much composition can make a component harder to use.

The first one is component composition. So what do I mean by this? Here we have a location card component that takes a couple props and renders this beautiful card that we see on the right. It looks quite nice actually and it's definitely simple and easy to use. The problem with this component is that it's not very flexible so if you want to make a simple change like hiding that add to favorites button that we see there on the bottom right, then our only option is to add another prop. That's not too bad. It's just an extra prop and the component is still quite simple and easy to use. But now we get a new requirement. We also want to make a change to the label of the button in some parts of the application. Okay, I guess we can add another prop for that. And this is still okay. But we have to be careful here because now we open the door for customizing anything in this component via its interface. So guess what's gonna happen next time someone else is asked to change the color of the button in some part of the app? They're gonna add another prop. We can see how this can get out of hand pretty quickly.

3. Improving Design with Component Composition

Short description:

Component composition allows for flexibility in React applications. By breaking down a large component into smaller ones, we can make changes without adding numerous props. However, too much composition can make a component harder to use.

So let's see how we can improve this design with component composition. We still have a location card component here that handles maybe the layout of the component. But now it doesn't take any props. Instead we're defining the contents of the card via child components. So now it's much more flexible. If you wanna hide the favorite button like we did before, you can just target that element in the JSX and remove it. And there we go. We wanna change the label and the color of the button. Well, the button is right there. So we can make the change in the button directly without having to change any other part of the component.

The important thing to keep in mind here is that with composition, there is usually a trade-off between flexibility and ease of use. We can break down a large component into lots of smaller ones, and this will make things super flexible. But with a lot of little pieces, the component becomes harder to use. At the other end of the spectrum, we have a very simple component that is not very flexible at all, like our initial example of the location car component. So usually, you want to be somewhere in the middle when it comes to composition. And this will give you the right level of composition for the problem that you're trying to solve.

Now how do we find this sweet spot? This brings me to my second point, which is to use the modules. This is a concept that comes from the book a philosophy of software design, which is one of my favorite books, and talks about module depth as a trade-off between cost and benefit. Any module, it could be a function, a class, or a React component has essentially two parts. It has an interface, which in the case of a React component, this is the props of the component. And it has a piece of functionality, which is what the module does. A deep module is one that provides powerful functionality via simple interface. A shallow module on the other hand, is one that provides very little functionality compared to the complexity of its interface. So let's see an example of a deep React component. This FileAbloader component, you know, it has a ton of functionality. It takes care of validation, drag and drop, the actual file uploading, including the error handling. And at the same time, the interface is very simple. It only has two props. The implementation of this component is probably very complex. But because of the simplicity of the interface, that complexity is nicely encapsulated.

4. Managing Complexity and Reducing Cognitive Load

Short description:

Component complexity can spread throughout the codebase. It's important to control the evolution of abstractions and reduce cognitive load. By minimizing the information needed for simple changes, we can make development easier. One example is declaring access rights in component lists. Other strategies include clear variable names, good comments, predefined design patterns, and documenting important decisions. Simplicity is difficult, but we can slow down complexity's growth.

It doesn't leak to the rest of the system. Compare that to this button component. One could argue that it doesn't do much compared to the complexity of its interface. More importantly, we can see that it's mixing several concerns here. We have the concerns of the button itself, but we also have the concerns of the icon and the tooltip. So not only the implementation of this component is likely very complex, but it's also spreading that complexity through the rest of the codebase.

It's important to know that nobody sets out to build something very complex from the start. Things usually start simple, like our initial implementation of the location card. This was a powerful component with a simple interface. It was a deep module, until it wasn't. So it's important that we control this evolution to make sure that the abstractions we implemented in the beginning are still the right solutions to the problems that we have today.

And this brings me to my final technique for managing complexity, which is aiming your efforts at reducing cognitive load. By this, I mean minimizing the amount of information you need to keep in your head to make a simple change. Take this component, for example. It's a grid of links to different parts of the app that users will see in their dashboards, but different users will see different links depending on which type of user they are and also on which plan they have. For example, a regular user on the basic plan will only see these four links. Now imagine we want to make a change, like adding a new link to this particular group of users. Here's what the implementation looks like. It's pretty clean, and I see that I have a list of links there, so I know that's where I need to go and add my new link. I see that we're filtering the links based on the user and the plan, which is great because that's exactly what I need to do, and then we're just rendering the links.

Now this filter links function seems important. I know I need to go in there and change the logic somehow so that it shows my new link to the right sets of users, and when I open this function we might find something that looks like this. Now this is a particularly messy implementation, so don't look too much into it. The important point here is that I now need to completely understand this filtering logic to make my simple small change. So I need to load all of this information in my head in order to make the change. Now imagine if the grid component looks something like this instead. It's very similar and I still have a list of links, but now you see that I can declare which users and plants have access to the link right in the list. So all I need to do is add a new item here and I can move on with my day. Notice that we're still calling the filter links function and for all I care about that function can be very very complex, but now I don't even need to open this function to make that change. I'm spared from having to load all of that logic in my head. This is just one example of how we can reduce cognitive load in our app. Other things we can do include using clear variable names so we don't have to guess what a component or a variable does, writing good comments that don't repeat the code but instead they explain the why and the how, using predefined design patterns because we already have a shared understanding of how they work, and documenting important decisions somewhere, particularly those high level decisions that they just don't belong in a code comment.

The core message of this talk is that simplicity is very hard. Making things easy to change and easy to understand is definitely possible but it's not easy at all. Complexity is an impossible enemy. We can never completely eliminate it but we do have some level of control over its growth and I hope that you can use some of the strategies that we covered today to slow it down before it takes over your code base. If you enjoy talking about complexity, software design and architecture you can find me online as charca in all the socials and also on my website frontend.skl where I write about software design and architecture for frontend developers.

Check out more articles and videos

We constantly think of articles and videos that might spark Git people interest / skill us up or help building a stellar career

React Advanced Conference 2021React Advanced Conference 2021
47 min
Design Systems: Walking the Line Between Flexibility and Consistency
Top Content
Design systems aim to bring consistency to a brand's design and make the UI development productive. Component libraries with well-thought API can make this a breeze. But, sometimes an API choice can accidentally overstep and slow the team down! There's a balance there... somewhere. Let's explore some of the problems and possible creative solutions.
React Summit 2022React Summit 2022
27 min
Walking the Line Between Flexibility and Consistency in Component Libraries
Design systems aim to bring consistency to a brand's design and make the UI development productive. Component libraries with well thought API can make this a breeze. But, sometimes an API choice can accidentally overstep and slow the team down! There's a balance there... somewhere. Let's explore some of the problems and possible creative solutions.
React Summit 2022React Summit 2022
20 min
Find Out If Your Design System Is Better Than Nothing
Building a design system is not enough. Your dev team has to prefer it over one-off components and third-party libraries. Otherwise, the whole effort is a waste of time. Learn how to use static code analysis to measure if your design system wins over the internal competition and data-driven ways to improve your position.
React Advanced Conference 2023React Advanced Conference 2023
19 min
Type-safe Styling for React Component Packages: Vanilla Extract CSS
Unlock the power of type-safe styling in React component packages with Vanilla Extract CSS. Learn how to effortlessly write scalable and maintainable styles, leveraging CSS-in-Typescript. Discover the seamless integration of Vanilla Extract CSS and take your React components to the next level.
JSNation 2023JSNation 2023
10 min
Dialog Dilemmas and Modal Mischief: A Deep Dive Into Pop-Ups
Our design systems commonly feature components that show on top of other content: tooltips, date pickers, menus and teaching UI, to name just a few. Proposed updates to the web platform are about to make building these a whole lot easier. You might not even need JavaScript. In this talk, you’ll learn all about the upcoming ‘popover’ attribute, modality and the top layer.

Workshops on related topic

React Advanced Conference 2022React Advanced Conference 2022
118 min
Rapid UI Development in React: Harnessing Custom Component Libraries & Design Systems
Workshop
In this workshop, we'll take a tour through the most effective approaches to building out scalable UI components that enhance developer productivity and happiness :-) This will involve a mix of hands-on exercises and presentations, covering the more advanced aspects of the popular styled-components library, including theming and implementing styled-system utilities via style props for rapid UI development, and culminating in how you can build up your own scalable custom component library.
We will focus on both the ideal scenario---where you work on a greenfield project---along with tactics to incrementally adopt a design system and modern approaches to styling in an existing legacy codebase with some tech debt (often the case!). By the end of the workshop, you should feel that you have an understanding of the tradeoffs between different approaches and feel confident to start implementing the options available to move towards using a design system based component library in the codebase you work on.
Prerequisites: - Familiarity with and experience working on large react codebases- A good understanding of common approaches to styling in React