JavaScript
Article
By Jack Franklin

How to Organize a Large React Application and Make It Scale

By Jack Franklin

An astronaut constructing a space colony in the shape of the React logo

This article is by guest author Jack Franklin. SitePoint guest posts aim to bring you engaging content from prominent writers and speakers of the Web community

In this article, I’ll discuss the approach I take when building and structuring large React applications. One of the best features of React is how it gets out of your way and is anything but descriptive when it comes to file structure. Therefore you’ll find a lot of questions on StackOverflow and similar asking how to structure applications. This is a very opinionated topic, and there’s no one right way. In this article, I’ll talk you through the decisions I make when building React applications: picking tools, structuring files, and breaking components up into smaller pieces.

Build Tools and Linting

It will be no surprise to some of you that I’m a huge fan of Webpack for building my projects. Whilst it is a complicated tool, the great work put by the team into version 2 and the new documentation site make it much easier. Once you get into Webpack and have the concepts in your head you really have incredible power to harness. I use Babel to compile my code, including React-specific transforms like JSX, and the webpack-dev-server to serve my site locally. I’ve not personally found that hot reloading gives me that much benefit, so I’m more than happy with webpack-dev-server and its automatic refreshing of the page.

I also use the ES2015 module syntax (which is transpiled through Babel) to import and export dependencies. This syntax has been around for a while now and although Webpack can support CommonJS (aka, Node style imports), it makes sense to me to start using the latest and greatest. Additionally, Webpack can remove dead code from bundles using ES2015 modules which, whilst not perfect, is a very handy feature to have, and one that will become more beneficial as the community moves towards publishing code to npm in ES2015.

Configure Webpack’s modules resolution to avoid nested imports

One thing that can be frustrating when working on large projects with a nested file structure is figuring out the relative paths between files. You’ll find that you end up with a lot of code that looks like this:

import foo from './foo'
import bar from '../../../bar'
import baz from '../../lib/baz'

When you’re building your app with Webpack you can tell Webpack to always look in a specific directory for a file if it can’t find it, which lets you define a base folder that all your imports can become relative to. I always put my code in a src directory. I can tell Webpack to always look in that directory. This is also where you need to tell Webpack about any other file extensions that you might be using, such as .jsx:

// inside Webpack config object
{
  resolve: {
    modules: ['node_modules', 'src'],
    extensions: ['.js', '.jsx'],
  }
}

The default value for resolve.modules is ['node_modules'], so you have to add it too else Webpack won’t be able to import files that you’ve installed with npm or yarn.

Once you’ve done that you can always import files relative to the src directory:

import foo from './foo'
import bar from 'app/bar' // => src/app/bar
import baz from 'an/example/import' // => src/an/example/import

Whilst this does tie your application code to Webpack, I think it’s a worthwhile trade-off because it makes your code much easier to follow and imports much easier to add, so this is a step I’ll take with all new projects.

Folder Structure

There is no one correct folder structure for all React applications – as with the rest of this article, you should alter it for your preferences – but the following is what’s worked well for me.

Code lives in src

To keep things organized I’ll place all application code in a folder called src. This contains only code that ends up in your final bundle, and nothing more. This is useful because you can tell Babel (or any other tool that acts on your app code) to just look in one directory and make sure that it doesn’t process any code it doesn’t need to. Other code, such as Webpack config files, lives in a suitably named folder. For example, my top level folder structure often contains:

- src => app code here
- webpack => webpack configs
- scripts => any build scripts
- tests => any test specific code (API mocks, etc)

Typically the only files that will be at the top level are index.html, package.json, and any dotfiles, such as .babelrc. Some prefer to include Babel configuration in package.json, but I find those files can get large on bigger projects with many dependencies, so I like to use .eslintrc, .babelrc, and so on.

By keeping your app code in src, you can also use the resolve.modules trick I mentioned earlier which simplifies all imports.

React Components

Once you’ve got a src folder, the tricky bit is deciding how to structure your components. In the past, I’d put all components in one large folder, such as src/components, but I’ve found that on larger projects this gets overwhelming very quickly.

A common trend is to have folders for “smart” and “dumb” components (also known as container and presentational components), but personally I’ve never found explicit folders work for me. Whilst I do have components that loosely categorize into “smart” and “dumb” (I’ll talk more on that below), I don’t have specific folders for each of them.

We’ve grouped components based on the areas of the application that they are used, along with a core folder for common components that are used throughout (buttons, headers, footers – components that are generic and very reusable). The rest of the folders map to a specific area of the application. For example, we have a folder called cart that contains all components relating to the shopping cart view, and a folder called listings that contains code for listing things users can buy on a page.

Categorizing into folders also means you can avoid prefixing components with the area of the app that they are used for. As an example, if we had a component that renders the user’s cart total cost, rather than call it CartTotal I might prefer to use Total because I’m importing it from the cart folder:

import Total from 'src/cart/total'
// vs
import CartTotal from 'src/cart/cart-total'

This is a rule that I find myself breaking sometimes – sometimes the extra prefix can clarify, particularly if you have 2-3 similarly named components, but often this technique can avoid extra repetition of names.

Prefer the jsx Extension over Capital Letters

A lot of people name React components with a capital letter in the file, to distinguish them from regular JavaScript files. So in the above imports, the files would be CartTotal.js, or Total.js. I tend to prefer to stick to lower-case files with dashes as separators, so in order to distinguish I use the .jsx extension for React components. Therefore, I’d stick with cart-total.jsx.

This has the small added benefit of being able to easily search through just your React files by limiting your search to files with .jsx, and you can even apply specific Webpack plugins to these files if you need to.

Whichever naming convention you pick, the important thing is that you stick to it. Having a combination of conventions across your codebase will quickly become a nightmare as it grows and you have to navigate it.

One React Component per File

Following on from the previous rule, we stick to a convention of one React component file, and the component should always be the default export.

Normally our React files look like so:

import React, { Component, PropTypes } from 'react'

export default class Total extends Component {
  ...
}

In the case that we have to wrap the component in order to connect it to a Redux data store, for example, the fully wrapped component becomes the default export:

import React, { Component, PropTypes } from 'react'
import { connect } from 'react-redux'

export class Total extends Component {
  ...
}

export default connect(() => {...})(Total)

You’ll notice that we still export the original component; this is really useful for testing where you can work with the “plain” component and not have to set up Redux in your unit tests.

By keeping the component as the default export it’s easy to import the component and know how to get at it, rather than having to look up the exact name. One downside to this approach is that the person importing can call the component anything they like. Once again we’ve got a convention for this: the import should be named after the file. So if you’re importing total.jsx, the component should be imported as Total. user-header.jsx becomes UserHeader, and so on.

“Smart” And “Dumb” React Components

I briefly mentioned the separation of “smart” and “dumb” components, and that’s something we adhere to in our codebase. Although we don’t recognize it by splitting them into folders, you can broadly split our app into two types of components:

  • “smart” components that manipulate data, connect to Redux, and deal with user interaction
  • “dumb” components that are given a set of props and render some data to the screen

You can read more about how we aim for “dumb” components in my blog post on Functional Stateless Components in React. These components make up the majority of our application and you should always prefer these components if possible, they are easier to work with, less buggy and easier to test.

Even when we have to create “smart” components we try to keep all JavaScript logic in its own file. Ideally, components that have to manipulate data should hand that data off to some JavaScript that can manipulate it. By doing this the manipulation code can be tested separately from React, and you can mock it as required when testing your React component.

Avoid Large render Methods

One thing we strive for is to have many small React components, rather than fewer larger components. A good guide to if your component is getting too big is the size of the render function. If it’s getting unwieldy, or you need to split it up into many smaller render functions, that may be a time to consider abstracting out a function.

This is not something that is a hard rule; you and your team need to get a sense of the size of component you’re happy with before pulling more components out, but the size of the component’s render function is a good guideline. You might also use the number of props or items in state as another good indicator. If a component is taking seven different props that might be a sign that it’s doing too much.

Always Use prop-type

React allows you to document the names and types of properties that you expect a component to be given using its prop-types package. Note that this changed as of React 15.5, previously proptypes were part of the React module.

By declaring the names and types of expected props, along with whether they are optional or not, you to have more confidence when working with components that you’ve got the right properties, and spend less time debugging if you’ve forgotten a property name or have given it the wrong type. You can enforce this using the ESLint-React PropTypes rule

Although taking the time to add these can feel fruitless when you do, you’ll thank yourself when you come to reuse a component you wrote six months ago.

Redux

We also use Redux in many of our applications to manage the data in our application, and structuring Redux apps is another very common question, with many differing opinions.

The winner for us is Ducks, a proposal that places your actions, reducer and action creators for each part of your application in one file.

Rather than have reducers.js and actions.js, where each contain bits of code related to each other, the Ducks system argues that it makes more sense to group the related code together into one file. Let’s say you have a Redux store with two top level keys, user and posts. Your folder structure would look like so:

ducks
- index.js
- user.js
- posts.js

index.js would contain the code that creates the main reducer, probably using combineReducers from Redux to do so, and in user.js and posts.js you place all code for those, which normally will look like:

// user.js

const LOG_IN = 'LOG_IN'

export const logIn = name => ({ type: LOG_IN, name })

export default function reducer(state = {}, action) {
  ..
}

This saves you having to import actions and action creators from different files, and keeps the code for different parts of your store next to each other.

Stand-Alone JavaScript Modules

Although the focus of this article has been on React components, when building a React application you’ll find yourself writing a lot of code that’s entirely separated from React. This is one of the things I like most about the framework; a lot of the code is entirely decoupled from your components.

Any time you find your component filling up with business logic that could be moved out of the component, I recommend doing so. In my experience, we’ve found that a folder called lib or services works well here – the specific name doesn’t matter but a folder full of “non-React components” is really what you’re after.

These services will sometimes export a group of functions, or other times an object of related functions. For example, we have services/local-storage which offers a small wrapper around the native window.localStorage API:

// services/local-storage.js

const LocalStorage = {
  get() {},
  set() {},
  ...
}

export default LocalStorage

Keeping your logic out of components like this has some really great benefits:

  • You can test this code in isolation without needing to render any React components.
  • In your React components, you can stub the services to behave and return the data you want for the specific test.

Tests

As mentioned above, we test our code very extensively and have come to rely on Facebook’s Jest framework as the best tool for the job. It’s very quick, good at handling lots of tests, quick to run in watch mode and give you fast feedback, and comes with some handy functions for testing React out of the box. I’ve written about it extensively on Sitepoint previously so won’t go into lots of detail about it here, but I will talk about how we structure our tests.

In the past, I was committed to having a separate tests folder that held all the tests for everything. So if you had src/app/foo.jsx, you’d have tests/app/foo.test.jsx too. In practice, as an application gets larger, this makes it harder to find the right files, and if you move files in src you often forgot to move them in test, and the structures get out of sync. In addition, if you have a file in tests that needs to import the file in src, you end up with really long imports. I’m sure we’ve all come across this:

import Foo from '../../../src/app/foo'

These are hard to work with and hard to fix if you change directory structures.

In contrast, putting each test file alongside its source file avoids all these problems. To distinguish them we suffix our tests with .spec, although others use .test or simply -test, but they live alongside the source code, with the same name otherwise:

- cart
  - total.jsx
  - total.spec.jsx
- services
  - local-storage.js
  - local-storage.spec.js

As folder structures change it’s easy to move the right test files, and it’s also incredibly apparent when a file doesn’t have any tests, so you can spot those issues and fix them.

Conclusion

There are many ways to skin a cat, and the same is true of React. One of the best features of the framework is how it lets you make most of the decisions around tooling, build tools and folder structures and you should embrace that. I hope this article has given you some ideas on how you might approach your larger React applications, but you should take my ideas and tweak them to suit you and your team’s preferences.

  • Enjoyable read—thanks Jack! There are lots of good ideas in here.

    Regarding the component naming and categorising them into folders: I, too, prefer this approach. Splitting components up by whether they’re smart or dumb never clicked with me, it seems very un-React-like, in that things that inherently relate to each other are arbitrarily kept away from each other. If you have several similarly-named components, you can always get around the confusion by using ES6 import aliasing:


    import { Header as SiteHeader } from 'global/header';
    import { Header as PostHeader } from 'post/header';

  • Bau

    Really nice reading :)

  • abimaelmartell

    Good ideas here. Thanks!

  • andrewingram

    You don’t need to tie yourself to webpack if you want the module aliasing, there’s a babel plugin which does the same thing, allowing you to do things like running your test suite without requiring a webpack config:

    https://github.com/tleunen/babel-plugin-module-resolver

  • Hi, does webpack’s modules resolution, broke flow-type, and quick access to file from IDE, or other tools ?

  • housecor

    Enjoyed the post Jack! One warning about module aliasing: It impacts editor autocompletion, since the editor can’t parse the path. Only webpack knows what the path means.

    • u can usually mitigate that by setting include paths in the IDE. just make sure it mirrors webpack

    • Atom + flow with similar resolve paths do the job well.

  • Kurt Farao

    Great podt thanks

  • Great post! One thing I might add is for Jest testing, we can also use moduleDirectories to resolve modules like webpack. This allows us to take out our test cases from `/src` and place all our test cases within the `/tests` folder (or anywhere we like)

  • Nice article, we are currently using an Atomic design approach at our project, it really helped to manage bigger groups of components, everything not having context with the application went into atoms, molecules, organisms..and everything context related is divided into modules, each module holding components, containers, reducers, tests…really easy to reuse anything this way :)

    • housecor

      Interesting. We’re doing something similar, but your module idea sounds like a point we’ve avoided thus far. People have asked for “smart” organisms that are basically mini apps which contain authentication, API calls, and other application specific concerns. We’re striving to keep our atoms, molecules, and organisms “dumb” so far. Why? Because if you have to organisms on the page that make API calls to get the same data, it leads to redundant API calls, and out-of-sync issues. Has this been a problem for you in practice? Interested to hear how you’ve handled.

      • When I looked at Atomic design there were 5 parts: atoms, molecules, organisms, templates and pages..I have something similar..while my templates are called modules..for every endpoint I have a module, it basically handles the API call, has its own reducer, container, actions and dump components. For instance I have a Products module I am reusing in more applications. The module is exporting 2 files: the reducer and the container so if I want to plug it into an app I will register the reducer and render the container into the corresponding section of the page. On componentDidMount, it will handle the API call and render a table of products. Most of the rendered components are atoms, molecules, organisms which are in a separate package. Also every module which is making an API call expects a url with a specified key to be accessible in the store. When the application is bootstrapped, we inject into the store a map of api urls so when our module triggers an api call the middleware will generate the endpoint address based on the name, parameters and the store url and handle the response. So far there were no issues with this approach, it really made it easy to decouple the logic and code and have reusable bits and parts. Once a module is complete, we review the components and try to move everything to the atoms, molecules…package so that the module is really just doing context specific tasks. We also have a node cli package which actually generates the module(files, constants, reducers, container and a default view..also with basic tests)..so this ensures consistency between modules and less writing of boilerplate :)

  • Adriano Souza

    It’s always cool see how other devs do the job, and a think what I will change is tests in same components’ folder, make much more sense for me.

Recommended
Sponsors
Get the latest in JavaScript, once a week, for free.