Max RozenMax Rozen
TwitterArticlesNewsletter

Guidelines to improve your React folder structure

If you’ve done any amount of research into the best way to structure your React app’s folders, you’ve probably run into the following advice copy-pasted into forum threads:

Move files around until it feels right.

To me that reads a lot like unhelpfully telling people to RTFM, so let’s develop a better answer than that (the quote originated from Dan Abramov in a long-ago deleted Tweet, and has since taken on a life of it’s own).

Folder structures for React apps

I’ll start by showing you the folder structure I tend to use, then teach you the guiding principles I use for my structure to “feel right” for my purposes.

My Project Structure

src/
|-- components/
|   |-- Avatar/
|   |   |-- Avatar.ts
|   |   |-- Avatar.test.ts
|   |-- Button/
|   |   |-- Button.ts
|   |   |-- Button.test.ts
|   |-- TextField/
|   |   |-- TextField.ts
|   |   |-- TextField.test.ts
|-- contexts/
|   |-- UserContext/
|   |   |-- UserContext.ts
|-- hooks/
|   |-- useMediaQuery/
|   |   |-- useMediaQuery.ts
|-- pages/
|   |-- UserProfile/
|   |   |-- Components/
|   |   |   |-- SomeUserProfileComponent/
|   |   |   |   |-- SomeUserProfileComponent.ts
|   |   |   |   |-- SomeUserProfileComponent.test.ts
|   |   |-- UserProfile.ts
|   |   |-- UserProfile.test.ts
|   |-- index.ts
|-- utils/
|   |-- some-common-util/
|   |   |-- index.ts/
|   |   |-- index.test.ts
|-- App.ts
|-- AuthenticatedApp.ts
|-- index.ts
|-- UnauthenticatedApp.ts

Guiding Principles

  1. On testing
  2. Naming your component files
  3. Separate your pages from your components
  4. Keep styled components in your component files
  5. Split things out only when you need to
  6. [Advanced] Split up your codebase by team

1. On testing

Many guides out there will suggest using a __tests__ folder whose structure mimics your app’s.

I strongly suggest you avoid this pattern for two reasons:

  1. You’ll discourage newcomers from testing by hiding your tests
  2. You’ll need to refactor to keep in sync every time you want to move your components/pages around

Instead, use either an index.test.ts file, or a MyComponent.test.ts file. Keep your tests with your components so you remember to test them.

2. Naming your component files

There are two ways you can name your component files:

  1. index.ts

    The advantage of naming your file index.ts is that that you don’t repeat yourself - you’ve already named the folder MyComponent/, why name the file the same?

    The disadvantage is that when you search for index.ts you’ll have dozens (if not hundreds, or thousands) of index.ts files, making it hard to find the file you’re looking for.

  2. MyComponent.ts

    The advantage of using MyComponent.ts is that VS Code and similar code search tools will find exactly the file you’re looking for faster, rather than listing all files in the directory.

It doesn’t particularly matter which way you choose to name your components, as long as you pick one, and use it consistently.

3. Separate your pages from your components

React frameworks like Next.js make this easier by using page-based routing, but you can use it in any React project.

For example:

src/
|-- components/
|   |-- Avatar/
|   |   |-- Avatar.ts
|   |   |-- Avatar.test.ts
|   |-- Button/
|   |   |-- Button.ts
|   |   |-- Button.test.ts
|   |-- TextField/
|   |   |-- TextField.ts
|   |   |-- TextField.test.ts
|-- pages/
|   |-- UserProfile/
|   |   |-- Components/
|   |   |   |-- SomeUserProfileComponent/
|   |   |   |   |-- SomeUserProfileComponent.ts
|   |   |   |   |-- SomeUserProfileComponent.test.ts
|   |   |-- UserProfile.ts
|   |   |-- UserProfile.test.ts
|   |-- index.ts

In your pages/index.ts file you can then keep your React-Router routes.

4. Keep styled components in your component files

I tend to keep my styled-components in the same file as my components. This is mainly to avoid context switching when trying to figure out what a component does.

You can also choose not to do this, up to you.

In practice, this looks something like this:

//DataChip.js
import React from 'react';
import styled from '@emotion/styled';

const Title = styled.p`
  font-size: 16px;
  line-height: 16px;
  font-weight: 700;
  margin-bottom: 4px;
`;

const Value = styled.p`
  font-weight: 700;
  font-size: 32px;
  line-height: 32px;
  margin-bottom: 0;
  color: ${(props) => {
    if (props.sentiment === 'GOOD') return '#30B17E';
    if (props.sentiment === 'BAD') return '#E94740';
  }};
`;

const ChipHolder = styled.div`
  @media (max-width: 600px) {
    margin-bottom: 8px;
  }
`;

export const DataChip = ({ label, value, sentiment }) => {
  return (
    <ChipHolder>
      <Title>{label}</Title>
      <Value sentiment={sentiment}>{value}</Value>
    </ChipHolder>
  );
};

In the past I would use CSS Modules, and keep the MyComponent.module.css file in the same folder as MyComponent.ts.

5. Split things out only when you need to

If you have a component that only gets imported by one other component, it either belongs in the same file that imports it, or at least in the same folder.

Don’t move things up into components/ that aren’t shared across multiple components. Also don’t move things to a higher level than they get used in general (such as moving a component-specific utility function into the src/ level utils).

We’re trying to organise our folders so that when you modify a folder, it has a minimal impact on other folders, and the impact of that change is predictable.

6. [Advanced] Split up your codebase by team

Eventually with a large enough project and engineering team, the CODEOWNERS file just doesn’t cut it, and it becomes necessary to split up your codebase by team.

Like all good things in tech, there are trade-offs:

  • On the one hand, your teams stop treading on each other’s toes as often (with merge conflicts, and implementing things differently to the team’s standard approach)
  • On the other hand, you risk creating silos and having teams re-implement work that another team has done

For example:

src/
|-- growth/
|   |-- components/
|   |   |-- GrowthSpecificAvatar/
|   |   |   |-- GrowthSpecificAvatar.ts
|   |   |   |-- GrowthSpecificAvatar.test.ts
|   |-- pages/
|   |   |-- UserProfile/
|   |   |   |-- Components/
|   |   |   |   |-- SomeUserProfileComponent/
|   |   |   |   |   |-- SomeUserProfileComponent.ts
|   |   |   |   |   |-- SomeUserProfileComponent.test.ts
|   |   |   |-- UserProfile.ts
|   |   |   |-- UserProfile.test.ts
|-- edgyTeamName/
|   |-- components/
|   |   |-- EdgyTeamNameSpecificAvatar/
|   |   |   |-- EdgyTeamNameSpecificAvatar.ts
|   |   |   |-- EdgyTeamNameSpecificAvatar.test.ts
|   |-- pages/
|   |   |-- UserList/
|   |   |   |-- Components/
|   |   |   |   |-- SomeUserListComponent/
|   |   |   |   |   |-- SomeUserListComponent.ts
|   |   |   |   |   |-- SomeUserListComponent.test.ts
|   |   |   |-- UserList.ts
|   |   |   |-- UserList.test.ts

At this point you might wonder whether a monorepo would make sense, but that opens up a whole new set of problems to solve, outside of the scope of this article.

Do you struggle to keep up with best practices in React?

I send a single email weekly with an article like this one to help improve the quality of your React apps. Lots of developers like them, and I'd love to hear what you think as well. You can always unsubscribe.

    Join 158 React developers who signed up last month!

    rssTwitterGitHub

    © 2020 Max Rozen. All rights reserved.