Refactoring a React App’s Design Pattern
How Stateful/Stateless design and Atomic design vie for my attention as I approach a major refactor.
How should I organize the components and directories in my React app? This is a hotly contested debate that exists between front-end developers. The end goal is to build out a structure that is easy to comprehend, scalable, and is of course modular. That much everyone can agree upon. We can also agree upon the fact that it is almost always easier and more efficient to decide on a design pattern at the beginning of a project and adhere to it, rather than switch halfway through and spend countless hours refactoring. The last statement is precisely why I am writing this blog today.
I was recently brought on to aid in the front-end development of a project by a good friend of mine. After a couple of weeks of work we began to realize that the design pattern we were using would soon become very difficult to manage. We were loosely adhering to a component/container, stateless/stateful model, but it was obvious to us both that a large refactor was on the horizon. We have a three-month roadmap in place, but if we don’t deal with this now, like a snarl in your hair or a knotted string of lights, it will only get more difficult and time-consuming to tackle later on. In other words, it may never get done if change isn’t affected now.
I am going to walk you through our original design, explain the reasoning behind my first attempt at refactoring the design (and why it was wrong), and then outline a more modern take on React app design that our we may use as the pattern for our project.
As I said in the introduction, the method we are currently employing is a stateful/stateless design pattern. For those who aren’t familiar with this pattern, it is quite simple. Divide the app into two distinct modules; containers and components. Bear with me while I map out something that is relatively common knowledge, because it will help in differentiating from the alternate approach we are considering later.
Containers are often classes. Containers are smart. They manage state, and they are connected to redux or use hooks. They often have very little in the way of JSX in the render( ) method of their own, save for a
<></> partials to cap the ends. Containers may also hold local state, make fetch requests, and pass props.
Components are stupid. Okay, that may be a bit harsh. Components are uninformed unless we inform them. Okay, that’s better. Components receive props from containers, have a greater amount of JSX in their render( ) methods, and are often functional as opposed to class-based. Components don’t know anything that isn’t told to them, and they are usually unaware of other components. Components also should be handling most of the logic for a given page. See, they aren’t such dummies after all. They just need input.
My partner and I have been using this method of design. Our
src directory is then broken up into two main directories of
components. While this is perfectly acceptable, we both began to notice two things kept happening that slowed our progress.
The first impediment is completely our own fault. We didn’t strictly adhere to our own methodology, and state often leaked into the components instead of being governed by the containers alone. The app functioned as expected, but this wasn’t enough. Were we to bring on more engineers, it would be excessively difficult to onboard them or inspire them to write code that is uniformed either. What’s more, is our naming patterns weren’t uniform. Regardless of which design pattern we eventually choose to adhere to, a refactor here would need to take place.
The second hold-up has more to do with the size of the project than anything else. When the project was small, managing a few components and a few containers in two directories was quite simple. However, now that there are 20+ of each, more in the pipeline, and even more once the aforementioned refactor takes place, we are going to be flooded with files existing in only two folders. As this happens it becomes more difficult to discern what each component is doing and what each container is managing. The user stories might still be in tact, but the way the story is written starts to become more and more difficult for an engineer to discern.
I am very much of the mind that good code isn’t just functional. It is legible. It is pleasing even to the uninformed eye. It tells a story without one having to break it down to a completely granular level. I also believe good design follows the same principles. If we are going to make our code adhere to this.. code, why not follow a design pattern that makes just as much sense and is just as intuitive?
A Good Idea 25 Years Too Late
So began the refactor. Something that I’ve learned about myself as an engineer is that I love clean code. In that same vein, I love refactoring code to make it cleaner. I’ve always been like that in other areas of my life though. When I cook I stick to the clean-as-you-go model. When the meal is prepared the only dishes left are the last ones I used. When I am in my wood shop I vacuum sawdust and organize my tools after each phase in the building process. It makes perfect sense that I like my code to be tidy and ready to go in each phase of writing it.
Unfortunately, simply enjoying the refactor isn’t enough. The design pattern I implemented resembled the way that one might organize their documents on their desktop. Unfortunately again, I spent more time on this than I care to admit.
A top-layer directory was followed by layers that increased in granularity for each component until I reached the lowest level, where the components were broken into each of their helper methods and functions. This is an organizational technique that we’ve all used our entire lives, so it seemed apropos. Experienced engineers already understand the problem with this, and perhaps you have already caught on as well.
Once this light bulb lit up I decided to hit pause and reassess. Time to bring in the big guns and ask another good friend of mine, who has been in the industry much longer, for some advice. His answer: Atomic design.
Atomic design is elegant in the way that it mimics life. I want you to think back to the lessons you learned taking chemistry in school. What was the smallest building block that matter consisted of? The atom! Of course, we know that there are much smaller particles that build atoms, but in terms of specific elements that can be differentiated, it’s the atom. If we were to combine a bunch of atoms together we would get a molecule. There are countless types of molecules that can be made by combining atoms in different combinations. Were we to take this one step further and combine multiple molecules together we would eventually arrive at an organism, right? We could keep going, but you’ll understand why we stop here very shortly.
Enter the atomic design pattern. Atomic design pattern bases the application structure on the above principle. Many atoms build a molecule, and many molecules build an organism. In atomic design there are five such stages in the design pattern. The first three just so happen to be named atoms, molecules and organisms. The pattern continues, but the metaphor stops here. As Brad Frost puts it in his book Atomic Design,
The language of atoms, molecules, and organisms carries with it a helpful hierarchy for us to deliberately construct the components of our design systems. But ultimately we must step into language that is more appropriate for our final output and makes more sense to our clients, bosses, and colleagues. Trying to carry the chemistry analogy too far might confuse your stakeholders and cause them to think you’re a bit crazy. Trust me.
So, to make things a bit more easy to understand the next two stages are templates and pages. The following are examples of each type of component in atomic design pattern.
The atom is the smallest unit that still represents a building block of the user interface. These include HTML elements such as buttons, form inputs, headers, images, etc. Alone they don’t really have any meaning, but one can still understand what they do.
A molecules is the combination of two or more atoms. A common example is a search bar. Usually the search bar includes a label, an input field, and a submit button. Combined together they represent a specific piece of functionality that is easily identified.
Organisms are combinations of two or more molecules. The navbar on your favorite website almost certainly is an organism when viewed through the atomic design pattern lens. It most likely contains a login/logout molecule, perhaps a set of dropdown link molecules that navigate the site, a home button, etc.
Templates abstract the design process out a bit further. Templates are page-level designs that place components and containers in specific locations to represent a final product. They don’t have any actual content though.
A page is the final stage of the atomic design pattern. This is where real-world content fills in the components that make up the template. This stage is what a user would interact with.
This design pattern speaks to my 6-year-old soul who spent hours building out LEGO fortresses. It speaks to the older climate scientist me who has a deep love of chemistry. It only makes sense that I utilize the same concepts as a software engineer.
Good question! What’s next is for my partner and I to do a deeper dive into the pros and the cons of each of these two design patterns and decide which one is going to work best for our project. I’ll be following up with another blog that walks through our decision process and the refactoring of the app from start to finish. Until then, don’t do what we did. Be explicit with the design pattern that you intend to structure your project with and stick to it. Your life, and the lives of future engineers who work on the codebase, will be made so much easier.
Below are some references that I consulted when doing research for this project.
Atomic Design Methodology | Atomic Design by Brad Frost
My search for a methodology to craft interface design systems led me to look for inspiration in other fields and…
REACT DESIGN PATTERNS
Container Components, Presentational Components & Functional Components