Structuring React.js Web Applications

The curse and the gift of React.js is the fact that it is not opinionated in terms of how you structure your components and files. Do what you want and it’ll work. But with every new project it’s a “blank slate” problem.

A Better naming and file organization system for React.js applications.

This article is written from a perspective of a person building (and finally refactoring) the front-end of a blogging platform designed for film photography enthusiasts. Besides listing and displaying articles, the app provides admin controls and a full rich text editorial suite, comprised of 280 files and folders. The app in question is Analog.Cafe.

Preface 1: “dumb” and “smart” components: not a good way to sort your files.

- app/ - components/ - containers/

When I began working on my application, coming from an intensive year with Ruby on Rails builds for Archie.AI (a fairly opinionated framework) I’ve hit the wall trying to figure out how to structure and name the numerous files with React. The most common advice at the time was to segregate the components into pure functions and stateful components.

The expectation with this method is that your components will inevitably get reused elsewhere. Although this may be true for many projects, I can’t see how it could be the case for all applications. Furthermore, ripping the functionality apart and placing the pieces of a component that performs the same function into separate corners of your filesystem is counter-productive and counter-intuitive.

With some experementation I’ve come up with a better system (below).

Preface 2: styled-components, they aren’t CSS; they are components.

styled-components is my library of choice when it comes to baking CSS into React projects. It’s very good.

A common pattern of usage with it is to separate styles into styles.js, a reminiscent of the file structure we used to have with simpler hand-written HTML files not too long ago.

Turns out this is not a practically good idea. Namely, because this method creates a third type of component (in addition to above-mentioned smart and dumb). What’s worse, this type of component has a completely separate organizational method, which attempts to mimic conflicting paradigms.

I recommend treating styled-components just as what they are — React components.

The Interface Pattern.

The Interface Pattern is a reminder that we are building a front-end application, which is easier to understand and structure when it’s thought of as a compilation of visual interface elements. This pattern consists of suggestions on file and folder structure, preferred export types, commenting practices, and file size recommendations.

File and folder structure shape.

- app/ - core/ - admin/ - user/ - constants.js - index.js - store.js - utils.js

Note: you can browse the entire application structure for Analog.Cafe in this repo.

My application is divided into three major sectionscore/ admin/ and user/. In your case, you may not have any sections if the app isn’t big enough. Then you can structure your app/folder just like the contents of the above sections (see below).

The four JavaScript files above should be self-explanatory, but with a few caveats. In this example they serve as index files, where they store only the most basic and commonly-used exports: index.js contains the main wrapper React component for the app, store.js combines the reducers found inside the above three application sections and exports a Redux store, while utils.js and constants.js contain the most common JavaScripit function snippets and reusable constants.

- core/ - components/ - constants/ - store/ - utils/

Inside each of the application sections are four folders which resemble the shape of the app/ directory. The only difference is that this shape forces you to create more files in your utils/and constants/folders which is better for the organization and should make tree-shaking work better too.

If you are not splitting your app into sections the above folders could be placed inside your app/ folder, instead of core/.

- constants/ - messages-.js - messages-article.js - routes-article.js - rules-submission.js - utils/ - actions-session.js - messages-profile.js

Both constants/ and utils/ folders have similar file-naming patterns. The first keyword is either messages, routes, rules, or actions. Followed by a dash and a keyword describing a specific part of your application view. I understand that this is not the most foolproof naming convention, however, you may be able to understand it much better in practice. The main objectives should be consistency and clarity.

Note the file named messages-.js which contains strings and objects designated as user-facing messages, not assigned to any specific part of the application view.

- store/ - actions-article.js - actions-submission.js - reducers-article.js - reducers-submission.js

The store/ folder is for Redux. It contains pairs of files (actions- and reducers-) for each part of your application view. Simple; all in one place.

- components/ - controls/ - icons/ - pages/ - routes/ - forms/ - vignettes/

components/ folder: I found the above six types of components to be fairly inclusive way to organize an application. It’s required to have such sub-folders to quickly find what you are looking for and understand application structure. Otherwise you may be stuck with hundreds of folders in this part of your app. This is how I distinguish them:

controls/Buttons & button arrays, modal boxes, links, nav bars, menus, etc.

icons/ — Graphic elements made with React and meant to stay as part of an app, such as integral SVGs or CSSs.

pages/ — Components that are meant to take over a whole or a meaningful part of a screen space.

routes/ — This folder is specifically for React Router route components.

forms/ — Input elements.

vignettes/ — Smaller components that do not belong anywhere else.

- controls/ - Card/ - index.js - components/ - CardFigure.js - CardHeader.js

Each of the component folders would have names written in CamelCase, with an optional index.js at their root, which would tie everything together. If necessary, components/ folder could be placed inside, which can contain styled-components or React.js components which would directly help compose the main component (in this case, Card/ component).

Note 1: There is no distinction or rule here between “smart” and “dumb” components, but the “smart” components naturally tend to end up at the root of the main component in index.js — which you could use to your advantage.

Note 2: There is nothing preventing you importing from files located in other application sections; a lot of the time it’s required and there’s nothing wrong with that. Feel free to require admin/ utils in your core/ components.

Note 3: You may have noticed that sub-components do not have their own folders. That makes for easier readability and better folder structure. They could, of course, be placed in their own folders if they in-turn have their own sub-components, but that would be messy. Try to keep your file tree as flat as possible.

Preferred export types.

A simple rule is to prefer named exports like export const function Name ()=>{} in constants/ utils/ and store/ — this will encourage you to balance the number of files in those folders nicely.

However, all components should strive to export only default exports with some exceptions where they could contain both defaultandnamed exports in very small files. This simple rule will force you to create more components (which are by the way a lot easier to name than more rigidly-structured constants and utils files), which in turn will create a number of benefits in terms of the final bundle size and app readability (fewer lines of code per file).

File size recommendations.

No more than 300 lines per file. Anything bigger than that warrants splitting it.

Commenting practices.

I used to think that more comments in the code is better. Until I learned otherwise. Plentiful comments could be useful when creating a tutorial, however, they tend to pollute application files and encourage bad variable naming practices. So if the code feels difficult to understand, it should be reviewed and corrected for a more comprehensible namings and style. Use tools like Prettier to your advantage.

Write readable code instead of something that you needs a manual.

It may seem like there’s a lot to deal with, but in practice, it could be easily achieved and understood by the whole team. Have a look at the repo that already uses this method to get yourself acquainted. Refer back to this text to get the details and the reasoning behind each choice.

As you may have noticed I haven’t mentioned anything about where to place tests. This method also hasn’t been tried in a great diversity of production systems so there may be some things I missed or got wrong. In those cases, you may have to adapt, and if you got time please let me know so that I could make this guide better.