I’d like to talk about a topic that perhaps doesn’t get spoken about as much as it should and because we don’t speak about it enough publicly it tends to be something that we learn through experience. Unfortunately this rarely happens in a painless fashion!
It is with this mind that I thought this week I would share my experiences on the topic of structuring our react projects. I’d like show you how I’ve structured them in such a way that they facilitate the evolution of my applications into the enterprise level applications we all hope to see our applications become.
Want to follow along but haven’t done the previous tutorials? Clone down this repo to give you a starting point. Once you’ve cloned it down run the command
npm installin the root of the project and you’ll be good to go!
What seems to be the problem here?
When we first start learning React, or any new technology for that matter, we find ourselves learning about the new tech in “a bubble”. Tutorial environments are always these nice sanitised, finely scoped spaces where of course…everything works perfectly.
Inevitably, in this contrived environment, we succeed in our objectives. So we take that success and use it to take our first steps as React journeymen and we begin using our new skills and cutting code. At this point most developers new to React wont have even considered that their project structure might be sub optimal. It’s not the developers fault it’s just because in the small domain that they’ve been working in the structure seems fine.
The structure I’m talking about will generally look something like this:
This structure looks perfectly fine and indeed, for now, it is. But the fact that all looks well is, unfortunately, kind of the problem.
You see this type of project structure has a tendency to not show it’s problems until the application has grown to such a scale as to become unmanageable.
It’s kind of like what Bob Martin describes in Clean Code (for UK shoppers). In that book he talks about how people continue to make poor decisions about the way their code is structured. They do this all in the name of gaining productivity at the cost of quality.
He explains that it’s a fallacy, because by the time you get to a point where the productivity gain you get from these shortcuts starts falling off you’ve already lost so much quality that a refactor is next to impossible. The only option is “The grand rewrite in the sky” and most of us have seen how that goes.
Our situation is even more tragic because in our case we are still getting all the negatives of the poor structure but it’s not even like we’re making a conscious decision about the path we’re taking! It’s simply that we don’t know any better at this point.
Falling further down the rabbit hole
When we see these giant lists of files I’m sure that I’m not the only one whose first thought is to split them all up into categories to make it easier to navigate them. So within each of our technical concerns we split the folders into functional domains.
In the small example presented this might seem contrived and unnecessary but remember; by the time the problems with this structure become apparent we’re already passed far into the realms of owning a project that is unmanageable.
So when the time comes to take this first course of action in Real Life™ it wont seem at all contrived. Rather it will seem like the only logical thing to do! It might look something like this:
There are three main problems with this structure which as it grows will begin to become apparent:
- It’s difficult to create a good workflow when working on a single component.
- Most often, decisions about functional domain organisation are arbitrarily made.
With this structure I’ve found that its really hard to establish for yourself a work flow that allows you to focus on one thing for any extended period of time.
The times in which you find yourself having to walk arbitrary paths to get to the next focus of your work seem to be a constant headache. Imagine that you are working to add a div to your scrollCreatorForm component in the structure above. You add your div and give it a css class and at this point you decide it’s best to give it a quick border before you move on.
You’ll find that to do this you have to grind you brain out of your current thought process and throw it crunching into the “folder tree traversal” gear. You’ll then be scanning the tree with your eyes to find the css folder so that you find the child scrollCreator folder before reaching your final destination, the scrollCreatorForm.css file. Now in this small structure that trip through your tree might seem like no big deal but as it grows by the time you’ve arrived at the css file you’ll probably find you’ve forgotten what you were going there for in the first place!
Functional domain organisation
You’ll see in this structure that the we’ve taken each of the components that look like they perform similar functions and lumped them into three loose categories. We have one for our single codeQuest component, one for all the components which deal with scrollCreation and a folder called common in which we place the components we think are going to get the most reuse.
Now there is going to be a Mr.Miagi moment a little later in this post so I’ll say again; the common folder is where we place components which we think are going to get the most reuse.
I’m sure you’re thinking these categories seem fine, but the problem with them is that they’re based on what we’ve observed the components doing when they’re performing their function in the application.
Categorising components this way is error prone because inevitably you’ll find that some components are common to any number of system areas. Where do you put these components that straddle your categorisations?
We can already see the start of this happening in the structure above; does the ScrollPrintPreview component belong in the scrollCreator functional area or does it deserve it’s own? Who knows? We could make a gut-feeling guess. Unfortunately the structure presented here really encourages us to do exactly that.
But come now, we’re developers! We should be preferring to group our components based on measured analysis of the code and deciding which components most often interact group them that way. But who has time to do all that?! Hint: that’s another Mr. Miagi moment in the making 😉
When you’re having to use relative paths to get up the tree and then come back down into the related component folder in a different technical area you will find yourself getting lost pretty quickly.
This problem is most pronounced when it comes to introducing tests that will have to “require in” your different components. Because inevitably your tests will end up in a tests folder, right? Which means requiring in your system under test is going to see you having to skip halfway across your project and back to find the source it needs to test.
There is also a knock on effect of all this in that your project structure becomes incredibly difficult to refactor. For example moving the ScrollPrintPreview component out of the scrollCreator area and into it’s own functional area is already going to require you to update some references but imagine if we were further along in the application and that print preview was being used in many places throughout the application.
All of sudden it seems like the sensible thing to do is avoid refactoring our project structure at all costs because it’s easier and less risky. This is a TERRIBLE place to be and Uncle Bob is laughing at you for ignoring his advice.
Less fretting, more fixing
Let’s stop being such negative nancys and talk about what I think the answer to all these ailments is. Do keep in mind whilst reading through this, that everything I say is based on my own personal experience so, in short, “your mileage may vary” on a project by project basis. I’d advise taking the bits you like and leaving behind the bits you don’t. Without further ado, this is what I think is one of the best ways to structure a react project:
It might not be completely obvious what I’ve done here. Allow me to point it out, basically I’ve turned the paradigm of splitting things technically and then by functional area on it’s head.
You’ll now see that our top level folders are codeQuest, common, multiTextInput and scrollCreator. Then within each of these functional areas you’ll see any combination of the folders named assets, components, containers and styles depending on what each component actually has in terms of source files. Let’s look at how this structure cures the ails of the previous structure.
I think you’ll find a much nicer development workflow when using this structure.
Let’s examine this in terms of the example problems given above. If I were adding a div and wanted to add styling for that div, BOOM, the styles are one folder away. Equally if I had a tests folder this also would be right next to my component so my testing workflow won’t be an issue either.
The traversal of the tree structure being a pain still remains in some ways, like if I needed to go from component to component. However, you will find you do far less traversal now because you’ll find it’s rare that you will be working with more than a handful of components in any one cognitive unit of work.
Functional domain organisation
By splitting things up into components like this, refactoring the structure of your project becomes far easier.
This is due to the fact that in this structure moving components around doesn’t necessarily mean having to rewire a heap of references within that component anymore. In moving a component all the relative references internal to the component are kept in tact and the only references you have to worry about updating are the ones in other components that take a dependency on the one that you moved.
Another massive bonus about this is that by allowing yourself to easily refactor, you buy yourself the ability to avoid having to make big decisions about the boundaries of domains within your application right now. Right now, you can choose the loosest domain boundaries that work. From there you can refactor as patterns emerge.
Also it was mentioned in the down sides of the previous structure that it was difficult to split out components for reuse in other project. This couldn’t be easier using this structure.
There isn’t much more to say here that isn’t obvious. Now that all the files within each component only need to reference files that are within the same folder; having distant relatives is just not a problem.
I like this effect so much that I’ve been known to not even make a tests folder within my component folders. Instead I just let the tests live right next to the components themselves in the components folder to help reduce having to make complicated relative paths even further.
Yes but will it
Finally let’s take a moment to talk about the biggest reason why I like this structure. It’s the fact that it has the ability to scale really, really well.
The reason it scales so well is a combination of the benefits given previously. On a component by component basis it is easy to reason about and it inherently helps promote reuse as well as bringing with it a strong ability to be resilient to refactoring.
What all this adds up to is a project that gives you the ability to defer decisions about it’s structure until you have the right information to make those decisions.
So with the structure above you’ll notice that for the most part the components that got split out into their own folders were the ones that had their own css files. The rest of the components have remained within sub folders under the common folder. I’m not suggesting that the criteria for a component to be split out is that it has a css file but in this particular project it seems to me that having a css file met the complexity threshold needed to justify splitting it out.
My workflow for building within this structure is that I will generally make new components within the common folder. The common folder then becomes a combination of things:
- It is a proving ground for new components to prove themselves as being something that deserves it’s own folder.
- It is a place to house much smaller, highly focused and highly reusable utilities and components that can be used throughout my app.
How do I decided which is which? That’s the beauty of it, I don’t. Because this structure lends itself so well to refactoring I can afford to just dump new components in common and then when I decided that they are getting complex enough to deserve their own folder I just refactor them out. This barely affects my ability to meet my development objectives because it’s so easy to do so I’m not scared to do it in the middle of other work.
The things that survive refactoring and go on to live in the common folder generally end up being those smaller, reusable type things that will probably always live there. Once again, no decisions needed to be made about the fact that they would live there it just happened organically during the process I’ve just described.
Finally, you also get the ability with this structure to fairly easily group similar components into logical areas of their own. As the number of your components increases you’ll find that they tend to group themselves into logic units that house components which tend to prefer to talk to each other.
Once you can identify these larger areas you should feel confident to place all these components together into a parent folder. Partly because it’s so easy to do and partly because over time you’ve organically collected experience about the business domain that will allow to make good decisions about this grouping.
Wanna talk more about it?
So rather than stepping through every step needed to refactor the tutorial application we’ve been building and risk boring you to tears I just went ahead and did the refactor for you an uploaded the results on to the tutorial github repo for you to peruse to see how the refactored project looks. Or if you’re feeling brave you attempt the refactor yourself using the tree structure pictured above.
If you have any comments about the structure I’ve presented here or would like to reach out to me to talk about this article more please don’t hesitate to comment below or catch me on any of my social accounts in the links at the top of the page. Otherwise, I’ll talk to you next week when we continue our code-venture together!