Translate

Monday, 2 November 2020

How to Organize a Large React Application and Make It Scale

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 Stack Overflow and similar sites 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.

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

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’s a complicated tool, the great work put into version 5 by the team 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 use ES Modules, first introduced in ES2015 (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. The majority of the web ecosystem has moved towards ES Modules, so this is an obvious choice for each new project I start. It’s also what most tools expect to support, including other bundlers like Rollup, if you’d rather not use webpack.

Folder Structure

There’s 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 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.

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 where they’re 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 they’re 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 '../cart/total'
// vs
import CartTotal from '../cart/cart-total'

This is a rule I find myself breaking sometimes. The extra prefix can clarify, particularly if you have two to three 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 lowercase 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. You can enforce this .jsx convention using a rule from eslint-plugin-react.

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 from 'react'

export default function Total(props) {
  …
}

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 default function Total(props) {
  …
}

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.

It’s worth noting that the one component per file rule isn’t always followed. If you end up building a small component to help you render part of your data, and it’s only going to be used in one place, it’s often easier to leave it in the same file as the component that uses it. There’s a cost to keeping components in separate files: there are more files, more imports and generally more to follow as a developer, so consider if it’s worth it. Like most of the suggestions in this article, they are rules with exceptions.

Continue reading How to Organize a Large React Application and Make It Scale on SitePoint.



No comments:

Post a Comment