How to structure your files in a large React application — the solution.

damusnet
7 min readJul 6, 2018

--

This article follows the previous one titled How to structure your files in a large React application — the problem. Hopefully you have read it first. In it we analyzed the traditional model of starting with actions / components / containers / reducers and the issues that come with it. In this second part, I’m going to propose a way of organizing files that will:

  1. Make it easier to discover code that belongs together
  2. Simplify file and component naming
  3. Facilitate code reuse and refactoring

A modest proposal

Let’s go back to our Ticketing application and see how we could make it better. Instead of using more top level folders, we are going to use less. Just one really. I like src but the name of this one folder doesn’t really matter. And then we will collocate everything together that belongs together in it.

To me that is the philosophy of React from the beginning anyway. First we collocated the markup (html) and the logic (JavaScript). Then we collocated the styles (css … -in-js). But for some reason we insisted on separating actions and reducers and other things.

Maybe it is because in its infancy, React didn’t have a full fledged state management solution. And then came flux, and then redux, and then so many others. And then people started worrying about splitting presentational and container components. Which is probably still a good idea, but splitting files is probably enough, there is no need to move them to separate folders entirely. Like school friends separated to opposite tables of the room by an angry teacher.

Here is what working on our Artist related code now looks like:

A code editor with a src folder containing the Artist, Home, Layout, Search, and Venue folders. Artist is expanded and contains the mocks, stories, tests, Events, Footer, and Header folders, as well as the actions, constants, helpers, index, queries, and reducer JavaScript files.

Everything is available in a single folder, easily discoverable. No need to wonder if this component uses redux or the Apollo Client for GraphQL (in this case it uses both for some weird reason. Hey, no code is perfect!). Fun fact, I was able to put many more useful files in this screenshot, while still needing less than the size of a full screen! Let’s review them one by one:

__mocks__

This folder usually has a single file, that contains a mock of the parent. In the case of Artist it is most likely a json object that either represents the response of an API call, or the state of the reducer after the call. Of course it can contain multiple objects as needed.

The reason it is separate from the __tests__ folder, is because mocks can be used for tests, yes, but also for stories, and also in the tests and stories of other components. Not just the ones of Artist.

__stories__

This folder contains a bunch of file that mirror the structure of the parent folder, but with a .story.js extension. So for /src/Artist/Header/index.js there will be a corresponding src/Artist/__stories__/Header/index.stories.js. Or alternatively, you can put all your stories in src/Artist/__stories__/index.stories.js. Or you can also put a __stories__ folder in your subfolders if that’s your jam.

__tests__

This folder contains all the tests, and follows the same pattern as the stories, except with a .spec.js extension. It truly is an awesome feeling when you start seeing stories and specs mirror each other, consume the same mocks, and give you the peace of mind that your code is covered, and will break loudly when it inevitably breaks during a refactor. Much smarter people than me have come up in favor of this, and Jest supports it by default.

actions.js, reducer.js, queries.js

Those files are specific to redux for the first two, and for graphql for the last one. Without much surprise, they contain the actions, reducer, and queries for the parent folder, in our case the Artist.

Now, I know that this is usually when people start asking questions. Like, if your reducer is in the Artist folder, does it mean that only the Artist container can use its store and actions? Well, no. Absolutely not. You still have a central store somewhere, with a combineReducer function, that imports this reducer. The location has changed, but the code still works the same. There is no convention that dictates that all reducers have to be in the same folder.

Likewise, any other component is allowed to import actions from this file. Sure, it will create dependencies between domains, but hey, this is the real world, and one of our objectives is to actually make code simpler to reuse. It doesn’t change anything to how it worked before. It is just easier to find out.

constants.js and helpers.js

Those files are less important, and you are free to rename them or not use them at all. They are just a pattern that kept coming back in my recent projects. Imagine a part of our app that has to deal with Location: the constants file holds things like export const MI = 'miles'; export const KM = 'kilometers'; that need to be reused in several places. You could keep them in your container or some other place, but extracting them makes things cleaner. It also encourages being explicit about values, instead of having random magic numbers here and there.

Likewise, the helpers file has tiny functions that might be reused (or not). For instance export const milesToKm = ({ miles }) => miles * 1.60934;. In our app both those examples would be in /src/Location/{constants,helpers}.js, instead of being in a utils or lib folder. By extracting helper functions, you also make them easier to test.

It is however really important to stick to the major pattern of having every file inside a domain folder, inside the src folder. You will be tempted to resort to the utils or lib folder, but that is a mistake. And the reason for that is:

Everything is a Component!

I cannot emphasize this enough! Literally. Medium won’t let me make the font bigger. But really, everything is a component. I have also used the word domain a couple of times already, but DDD is a real thing, and I don’t know that thing, but I know React. So let me say it again:

Everything is a Component!

Back to our Location folder. Maybe you don’t actually have a Location container, or a Location presentational component. But that’s okay. You can still have a Location folder, with its constants and helpers and its __tests__ and who knows, maybe one day it will have a container to show its beautiful UI. It will probably get an action / reducer pair first. But in any case, that’s okay. Some heroes don’t wear capes, and some components don’t have a UI. But in the end, everything is a component.

index.js, Header, Events, Footer

The index file is most likely your container, while the other folders contain the presentational components. They are not required to have their own individual folder. It just looked better and less confusing for my screenshot.

The way files and components are named though, matters a whole lot more. More on that now.

Naming files and components

Please keep in mind that this is just a proposal. Not a specification, not a standard, and nothing will break if you change it. In fact, please make it your own.

We all know that naming things is one of the two hardest challenges in computer science, along with cache invalidation, and off by one errors.

Naming files and components in a large and growing React application is no exception. You would be surprised at how many different Header components a small app can need. Here are a few suggestions to make it easier.

As counter intuitive as it might seem, having globally unique names actually makes things easier. In fact, I think I recall someone at Facebook saying that all their thousands of components have unique names, and can be imported without typing the full path because of that. Likewise, ReasonML requires unique names, and as a consequence doesn’t even require imports at all (pun intended)!

A code editor with three React component JavaScript files open: src/Artist/index.js, src/Artist/Events/index.js, src/Artist/Events/List.js

Hopefully, the relationship between a file name and its component name should be obvious. Every component is named after its full path, starting after /src folder, if the file name is index.js, and takes on the name of the file in addition to that if not.

  1. /src/Artist/index.js is <Artist />
  2. /src/Artist/Events/index.js is <ArtistEvents />
  3. /src/Artist/Events/List.js is <ArtistEventsList />

By using the path and file name, and since every file or component is in the src folder, naming becomes easier, and components have globally unique names out of the box. It makes things much more straightforward when you look at the React Dev Tools and try to figure out which component is which. Their name is their exact location in the codebase!

Does it work? Does it scale?

Yes.

No, seriously, it works. I’ve tried it! Alone ; in a team of three ; in a team of six or more ; with junior and senior developers. I don’t ever want to go back. Especially with things like render props, the Apollo Query Component, and the upcoming React Suspense API where … everything is a component. Do I need to say it again?

This file structure:

  1. Makes code that belongs together easier to discover
  2. Simplifies the naming of files and components
  3. Facilitates the reuse and refactoring of components

That’s all folks!

Please like and subscribe. But more importantly, let me know if you have tried it, liked it or not, or if you have another file structure that works for you. I’m pretty sure I’ve seen projects in the wild do the same thing. It is probably close to the redux ducks pattern (though really not specific to redux). And I definitely haven’t invented anything new or ground breaking there. But I’m always surprised at finding the traditional actions / components / containers / reducers structure and wanted to have something to point to in the future. Let me know also if you have found a similar idea expressed in less words.

Many thanks to Julien and Guillaume who read drafts of this post and helped me improve it.

--

--