Colocating React component files: the tools you need

David Barral
Trabe
Published in
4 min readMay 27, 2019

--

Photo by Steve Johnson on Unsplash

React does not force any file structure to work. File structure is one of those themes up for debate where no choice can be the gold standard.

In general, it is a good idea to keep files that often change together close to each other. This principle is called “colocation”. (React docs)

This post is not about why you should or shouldn’t colocate. That’s up to you. We do colocate our component files. That’s our choice. So, this post is about how we do it and how we configure our tools to support it.

Our current component layout

We put each component in its own folder. This folder is named after the component using kebab-case. Casing is not relevant. I won’t debate whether is best to use kebab, snake, pascal, camel or any other casing you may love. Component folders are arranged inside other folders to define a taxonomy.

Each component folder contains several files, also named after the component. Each one with its own distinctive extension:

  • .jsx for the component’s code. The component is always the default export.
  • .module.css for the component styles. We use CSS modules and BEM conventions.
  • .test.jsx for the component’s tests. Jest is our testing framework of choice.
  • .test.jsx.snap for the test snapshots.

We also add an index.js file that re-exports the component to have shorter import sentences: import Component from "path/to/component"; instead of import Component from "path/to/component/component.jsx";.

Example, a simple button component

Our SimpleButton will be grouped under buttons and has the following layout:

components
└── buttons
└── simple-button
├── index.js
├── simple-button.jsx
├── simple-button.module.css
├── simple-button.test.jsx
└── simple-button.test.jsx.snap

simple-button.jsx defines the component. In this example a SimpleButton that can be either enabled or disabled:

simple-button.module.css contains the CSS modules for the component. Selectors follow our own JS friendly BEM naming convention:

The index.js re-exports the SimpleButton component from simple-button.jsx:

And, lastly, simple-button.test.jsx contains the Jest test suite for the component. Notice we import SimpleButton from ".". This way we make sure the index.js re-export is working as expected.

Colocation, following this layout, gives us several advantages:

  • Component files are easy to find.
  • Trivial imports from test to component, component to assets, etc.
  • Easily change the components taxonomy. To move a component, just move its folder around.
  • Easy extraction of components to their own packages. Once again, you just need to move the folder around.

Depending on which tools we are using to document our components we can have more colocated files (.md or .mdx for example. We use both React Styleguidist and Docz, depending on the project). We are not covering this topic today, but it also benefits from colocation.

Configuring the tools to fit this layout

Your everyday JS tools does not encourage colocation by default, or, at least, not this way. Don’t worry, making them work the way we want is just a matter of tinkering with the config.

Babel: allowing default exports

The current standard syntax for export does not allow to re-export from another package the way we do in our index.js. It needs more boilerplate code unless you use Babel and @babel/plugin-proposal-export-default-from. It implements the stage-1 proposal to support the export from syntax.

Webpack: supporting CSS modules

To use CSS modules, webpack must use the css-loader with modules enabled. We setup the loaders for the .css imports overriding the .module.css imports to use CSS modules. This way we can import some third party stylesheet or setup some global styles avoiding the CSS modules scoping.

Jest: collocating test and snapshots

By default Jest will look for your component test in a __test__ folder and store snapshots in __snapshots__. You can change this behaviour using the testRegex and snapshotResolver config options. With testRegex you state that your test will be named after the component file and use a .test.jsx extension.

The custom snapshotResolver tells Jest to store the snapshots in the same folder as the test, adding a .snap extension.

Jest: supporting CSS modules

If you use CSS modules you must tell Jest how to handle your .css imports by configuring a moduleNameMapper.

We use identity-object-proxy. The styles you import in your component will be an ES2015 proxy that returns the keys you use as a string: styles.Button will be "Button". This way the classNames in the snapshots will be meaningful.

ESLint: applying rules only to test files

Last but not least, we use ESLint to check our code. We can apply certain linting rules only to our test files using the overrides option. We use it to enable Jest globals and setup some eslint-plugin-jest rules.

Summing up

Colocating component files brings several advantages. We strongly believe that it is the better approach and we are happy with it. You can develop your componentes following this layout too, just by making simple changes to your tooling configuration. Give it a try. You won’t go back.

--

--

David Barral
Trabe
Editor for

Co-founder @Trabe. Developer drowning in a sea of pointless code.