July 01, 2021

How I Structure My React Projects

edit

Recently, my friend Chance and I had this exchange on Twitter:

It’s true. It really doesn’t matter. That said, I’m going to share a few of my preferences and why they are what they are.

Warning: These are my opinions. You don’t have to agree with them. If you don’t agree with them, that’s ok. Just know, I’m not going to engage in an argument with you about these opinions.

Single file components

Don’t have a folder named MyComponent with an index.tsx, styles.ts|css, and types.ts file, etc.

Do have a file named MyComponent.js|jsx|tsx and have all related styles and types in the same file.

Why?

First, it is a waste of time and annoying AF to have to search for a file in my editor by typing MyComponent followed by index to ensure I get the right file.

This is one of my least favorite features of “convention over configuration” frameworks like Ember and should be avoided in React projects. You end up with hundreds and hundreds of files that have the same name, index.js or component.js and you always have to do two-part searches. What an absolute waste of keystrokes. I know you may think that’s trivial, but it adds up when you’re searching for dozens and dozens of files every day. It’s never good enough to just do MyComponent. You need to add styles or types or use arrow keys to navigate a list to get the file you were looking for. What a waste!

Second, having files that only exist to export tightly coupled information, like styles and types is equally wasteful and can lead to some organizational footguns. Put the info directly in the same file. You’ll convey information faster, improve Intellisense speed, and more. Plus, you get the bonus of not creating an export unless other modules actually need that value. When you do the multi-file approach, you run the risk of people importing styles and types in an ad hoc fashion and it’s a fast way to running into duplication and organizational messes.

Sub-components in same file, too

Don’t make a separate file for a small sub-component that won’t be reused by another part of the system.

Do use sub-components where it makes sense, but keep them in the same file.

Why?

Related to single file components, if you are breaking up a bigger component into sub-components and those sub-components are relatively small and are not intended for reuse, keep those in the same file as well.

function BigComponent(props) {
  return (
    <div>
      <SubComponentA />
      <SubComponentB someProp={props.someProp} />
    </div>
  )
}

function SubComponentA() {
  return <div>Sub A</div>
}

function SubComponentB({ someProp }) {
  return <div>{someProp}</div>
}

So long as SubComponentA and SubComponentB have no reason to be exported or used elsewhere, there’s nothing wrong with keeping them within the same file. In fact, it encourages you to break your monolithic components down into reasonable and understandable pieces.

There is one downside to this if you’re using TypeScript. You will have to re-declare the types of your props for every component you do some prop drilling for. Given that you can often define a Props type, and then reuse the types in it with Pick<> or something similar, I find this to be a reasonable tradeoff.

Feature folders

Don’t just have a folder named components and throw everything in there.

Do have a folder named features where each sub-folder is a specific feature containing all the files related to that feature.

Why?

I think when you’re first starting a project or doing a side project, it’s fine to have a single components folder and throw everything into it. But soon enough, you’ll realize you have a set of components that are shared and many components that are really just one-off abstractions for a single feature. Keeping these in the same folder misrepresents how these components should be used in your system.

I have found it better to take those components related to a single feature to give that feature a name and contain all the files for that feature in that folder. Doing so, allows you to treat the feature similar to a package, exporting precisely what you intend to the rest of your system. You can even write eslint rules that prevent people from importing values that are nested further than the top level of your features folder. It forces you to think about the interfaces of your system and to define useful boundaries.

This also has the benefit of more accurately representing your organizational information. I have found this to be a useful means of helping people onboard to complex projects. Rather than asking them to try and piece everything together from across your codebase, it is easy to pick a feature and explore that sub-tree of your system.

Summary

Don’t create extra files just to export something that’s only used by a single component, like styles and types. Use single file components to encourage exporting precisely what the rest of the system needs and improve file search by a few keystrokes each time.

Similarly, keep sub-components in the same file until they become useful abstractions for the system.

When you do have these useful components to break out, determine if they belong to part of your shared system , or really belong solely to a feature. Use feature folders to identify boundaries in your system.


Liked the post?
Give the author a dopamine boost with a few "beard strokes". Click the beard up to 50 times to show your appreciation.
Tags
Kyle Shevlin's face, which is mostly a beard with eyes

Kyle Shevlin is the founder & lead software engineer of Agathist, a software development firm with a mission to build good software with good people.

Logo for Introduction to State Machines and XState
Introduction to State Machines and XState
Check out my courses!
If you enjoy my posts, you might enjoy my courses, too. Click the button to view the course or go to Courses for more information.
Sign up for my newsletter
Let's chat some more about TypeScript, React, and frontend web development. Unsubscribe at any time.