How to Organize a Large React Application

How to Organize a Large React Application

The best practices for creating and organizing React applications in a scalable way. Tt is very easy to set up a small React application, or to start one from scratch. Especially if you use create-react-app. How to organize files and folders? How to enforce a style guide? What type system, linter and tester to use? How to maintain code quality?

Nowadays, it is very easy to set up a small React application, or to start one from scratch. Especially if you use create-react-app. Most projects probably just need a few dependencies (for example, for state management and for internationalization) and a src folder with at least a components folder. I guess this is how most React projects start. Typically, though, as the project grows the number of dependencies, components, reducers and other shared utilities tends to increase in a, sometimes, not so controlled manner. What do you do when it is no longer clear why some dependencies are needed or how they work together? Or when you have so many components it becomes difficult to find the one you need? What do you do when you want to find a component but you don’t really remember its name?

These are just some examples of the questions we came across while rebuilding our frontend at Karify. We knew that the number of dependencies and components was eventually going to grow out of hand. That meant we needed a plan that would be scalable enough to keep up with future development. This plan involved defining conventions for our file and folder structure, code quality and also defining the overall architecture. Most importantly, all this should be easy to pick up by new developers without requiring them to have much insight into all our dependencies and code style.

At the moment of writing, we have about 1200 JavaScript files of which 350 are components with 80% of unit test coverage. Since we still believe in the architecture and conventions we created we thought it would be a good idea to share them. In the following sections, I’ll go through how we set up our project and also some of the lessons we learned.

How to organize files and folders?

We went through multiple phases until we figured out how we wanted to organize our React frontend. Initially, we thought of placing it in the same repository as our jQuery frontend. However, the imposed folder structure of our backend framework made this option undesirable. Next, we thought of moving it into a separate repository. This worked well at first, but with time we started thinking about creating other frontends, such as a React Native frontend, which motivated the need for a component library. This led us to split this new repository into two separate repositories: one for the component library and one for the new React frontend. Even though this seemed like a good idea, it resulted in a very complex review process. The relationship between changes in the two repositories became unclear. Finally, we chose to bring them together again into one repository, but this time as a monorepo.

We chose a monorepo because we wanted to create a separation between our component library and our frontend applications. The difference between our monorepo and others out there is that we don’t really need to publish the packages inside of it. For us, the packages serve only as a means for modularity and separation of concerns. It is especially useful to have different packages for every different application since we can specify different dependencies and scripts for each one of them.

We set up our monorepo using yarn workspaces with the following configuration in our root package.json:

"workspaces": [
    "app/*",
    "lib/*",
    "tool/*"
]

Now some of you might wonder why we didn’t simply use a packages folder like in other monorepos. Mainly because we wanted to create a separation between our applications and our component library. Besides that, we also knew we needed to create some tooling of our own. So, we came up with the folders you see above and here is an explanation for each one of them:

  • app: all packages in this folder refer to frontend applications like our Karify frontend, some internal frontends and also our Storybook;
  • lib: all packages in this folder expose shared utilities to our frontend applications and are as application-agnostic as possible. These packages basically form our component library. Some examples would be our typography, media and primitive packages;
  • tool: all packages in this folder are Node.js specific and either expose tools that we created ourselves or are configuration and utilities for tools we depend on. Some examples would be webpack utilities, linter configurations, and a file system linter.

Regardless of where we place them, all our packages always have a src folder and optionally have a bin folder. The src folder of our app and lib packages might contain some of the following folders:

  • actions: contains action creator functions of which the return value can be passed to the dispatch function from redux or useReducer;
  • components: contains component folders with their respective definition, translations, unit tests, snapshots and stories (if applicable);
  • constants: holds constant values reused across different environments and also different utilities;
  • fetch: holds type definitions for the payloads from our API and the respective async actions to retrieve them;
  • helpers: holds utilities that do not fit in any of the other categories;
  • reducers: contains reducers to be used in our redux store or useReducer;
  • routes: holds route definitions to be used in react-router components or history functions;
  • selectors: contains helper functions that read or transform data from our redux state or our API payloads.

This folder structure allows us to write really modular code since it creates a clear separation of concerns between different concepts defined by our dependencies. This helps with the lookup of variables, functions, or components in the repository, independently of us knowing if they exist or not. Furthermore, it also helps keeping the contents of these folders to a minimum, which in turn makes them easier to process.

One challenge with this new folder structure is to make sure we stick to it. It is tempting to start creating different folders in different packages and organizing files differently between them. While that might not always be a bad idea we would end up with a mess if we don’t do it consistently. To help with that, we created a file system linter which I’ll describe in more detail in the next section.

How to enforce a style guide?

In the same way that we wanted to have a consistent file and folder structure, we also wanted to have as much consistency in our code as possible. This is something that we already did quite well in our jQuery frontend but it could be improved especially when it came to CSS. Therefore, we tried to define a style guide from the beginning and to enforce it with a linter. Rules that can’t be specified with a linter are enforced during code reviews.

Setting up a linter in a monorepo is the same as in any other repository, it is great because you can run it once to validate the entire repository. If you are not familiar with any linters I recommend you take a look at ESLint and Stylelint which are the ones we use.

Having a JavaScript linter proved especially useful for the following use cases:

  • Enforcing the use of accessibility aware components over their HTML counterparts: during design, we defined several accessibility guidelines for anchors, buttons, images, and icons. Then in code, we wanted to implement those guidelines and make sure we don’t forget about them in the future. We did this with the react/forbid-elements rule from eslint-plugin-react. Example of what it looks like:
'react/forbid-elements': [
    'error',
    {
        forbid: [
            {
                element: 'img',
                message: 'Use "<Image>" instead. This is important for accessibility reasons.',
            },
        ],
    },
],
  • Forbidding imports of application packages from inside library packages and forbidding the import of application packages inside other applications: mainly to avoid circular dependencies between packages in the monorepo and to make sure we stick to the separation of concerns we created. We forbid this with the import/no-restricted-paths rule from eslint-plugin-import.

In addition to JavaScript and CSS linting, we also have our own file system linter. This is how we make sure that we stick to our folder structure. Since this linter is ours we can always change it if we decide to change the structure. Here are some examples of the rules we have:

  • Validate the structure of our component folders: make sure there is always an index.ts and a .tsx file with the same name as the folder.
  • Validate our package.json files: make sure there is always one per package and that it is set to private in order to avoid us publishing packages accidentally.

react javascript programming developer

Bootstrap 5 Complete Course with Examples

Bootstrap 5 Tutorial - Bootstrap 5 Crash Course for Beginners

Nest.JS Tutorial for Beginners

Hello Vue 3: A First Look at Vue 3 and the Composition API

Building a simple Applications with Vue 3

Deno Crash Course: Explore Deno and Create a full REST API with Deno

How to Build a Real-time Chat App with Deno and WebSockets

Convert HTML to Markdown Online

HTML entity encoder decoder Online

How native is React Native? | React Native vs Native App Development

Article covers: How native is react native?, React Native vs (Ionic, Cordova), Similarities and difference between React Native and Native App Development.

Hire Dedicated React Native Developer

Have you ever thought of having your own app that runs smoothly over multiple platforms? React Native is an open-source cross-platform mobile application framework which is a great option to create mobile apps for both Android and iOS. **[Hire...

Hire Dedicated React Native Developer in India | React Native Development

Hire dedicated React Native developers for your next project. As the top react native development company we offer you the best service as per your business needs.

Hire React Js Developer from Expert React JS Development Company

Are you looking to [hire React JS developer](https://www.mobiwebtech.com/react-js-development-company/ "hire React JS developer") from a prestigious and reliable React JS development company? Visit at Mobiweb Technologies here we have a big team...

A Short Guide to React Native App Development

React Native is undoubtedly one of the most widely used cross-platform frameworks for creating native-like apps. This framework can be easily used for developing brand-new apps from scratch and even in existing iOS or Android projects.easily used for developing brand-new apps from scratch and even in existing iOS or Android projects.