Writing Scalable React Apps with the Component Folder Pattern

Discover how to organize your React components using the component folder pattern. It will help un-clutter your projects, and your life. It’s soon to be your new best friend.

Photo by Wesley Tingey on Unsplash

We’ve all seen them. The huge 400+ line god component behemoths. They can be so large, that it takes the better part of a day to get a high-level understanding of what they do.

In this article, we will take one of these god components and break it down into bite sized and logical units of work. Then we’ll apply the component folder pattern to compartmentalize the pieces.

Giphy search

For our example, I’ll be using a Giphy search app whose file structure looks like this.

src/
|- App.js
|- GiphySearch.js

It looks pretty simple, but everything (state, view, and data loading) are all in one component. This makes GiphySearch hard to maintain.

The before Giphy search app on CodeSandbox.io. It works, but it’s not pretty.

You can start to imagine the sub-components that we’re going to need. First we’ll need a controller that keeps the state (e.g. loading, error, and data). We’ll also need:

  • A SearchInput component that renders the input box and calls an onSearch handler when the user clicks “Search”.
  • A Loading component that displays a loading indicator.
  • A loadData module that sends a search query to the Giphy API and fetches the image URL.
  • An Image component that displays the image URL.
  • An Error component in case something goes wrong.

I’ll add one more component, a View component that selectively dispatches to the other render components. Our file system would now look something like this.

src/
|- App.js
|- GiphyView.js
|- GiphySearchInput.js
|- GiphyImage.js
|- GiphyLoading.js
|- GiphyError.js
|- giphyLoadData.js

A big problem with this approach is that as we add components, our src folder will start to fill up fast. There will be lots and lots of files, making it hard to find what you’re looking for. But to me the issue is that we are using a namespaced file structure. Guess what? That’s exactly the problem that folders solve.

Introducing the component folder pattern

What can we do to logically structure all of these files? What if we placed our related files together into a single GiphySearch folder? This is what we do for traditional web development with an index.html and its supporting files. It would look like this.

src/
|- App.js
|- GiphySearch/
|- index.js
|- View.js
|- SearchInput.js
|- Image.js
|- Loading.js
|- Error.js
|- loadData.js

Nice, but what is that index.js file doing here and what does it do? Well notice that our GiphySearch.js file is now gone. We simply renamed it index.js.

The thing I like about this approach is that once you get your GiphySearch component working, collapse the folder. You’ll no longer be staring at all of those files, reducing visual clutter. Researchers at Princeton University Neuroscience Institute conducted a study that suggests reducing clutter can help you stay focused.

src/
|- App.js
|- GiphySearch/

Ahh… much better. Looks a lot like the file structure that we started out with, doesn’t it?

Some of you are asking, “Will I have to change my import statements with this new file structure? After all, the code for Giphy Search is inside of a folder and in a file with a different name.” The answer is… NO! That’s the beauty of the component folder pattern.

Your app still looks something like this, no matter if the module lives in GiphySearch.js or GiphySearch/index.js. Hurray for science!

import GiphySearch from './GiphySearch';
const App = () => (
<GiphySearch initialQuery="dog"/>
);

Before we dive in and look at the code line-by-line, here is what our new Giphy search looks like.

The after version, using the injected SpinLoad. Try it out and see the code on CodeSandbox.io

Breaking it down

Let’s look at our completed GiphySearch component and what is inside of each file. We’ll start with index.js. It’s sole reason for existence is to maintain state. Nothing else.

import View from './View';
import loadData from './loadData';
export default class extends Component {
state = {};
load = this.load.bind(this);
  async load(...args) {
try {
this.setState({ loading: true, error: false });
const data = await loadData(...args);
this.setState({ loading: false, data });
} catch (ex) {
this.setState({ loading: false, error: true });
}
}
  render() {
return (
<View {...this.props} {...this.state} onLoad={this.load} />
);
}
}

Both props, state, and an onLoad callback, are passed down to the View component. How we get the data is handled by the loadData module. This is because neither have anything to do with state.

In fact, look closely. Nothing about this component has anything to do with a Giphy search. It is abstract in about every way possible. Something you could use over and over again. IMO, this is simplistic elegance.

“A component should do one thing, and do it well”

Next is the View component. It will determine what to render. It’s really more of a view controller, as it passes along to other render components to do the actual rendering ﹣all based on state passed in as props from index.js.

import SearchInput from './SearchInput';
import Image from './Image';
import Loading from './Loading';
import Error from './Error';
const View = ({ 
loading, error, data, initialQuery, onLoad,
RenderSearchInput, RenderImage, RenderLoading, RenderError,
}) => (
<div>
<RenderSearchInput
initialQuery={initialQuery}
onSearch={onLoad}
/>
<section>
{do {
if (loading) {
<RenderLoading />
} else if (error) {
<RenderError error={error}/>
} else {
<RenderImage src={data} />
}
}}
</section>
</div>
);
View.propTypes = {
...
};
View.defaultProps = {
RenderSearchInput: SearchInput,
RenderImage: Image,
RenderLoading: Loading,
RenderError: Error,
};

Notice that we use component injection to render based on loading, error, and data conditions (RenderLoading, RenderError, and RenderImage respectively). Defaults are provided if none are specified.

Next is the loadGiphy file. It uses fetch to hit the Giphy REST endpoint, converts the JSON to an object, and extracts the image URL. It’s rather standard, so I won’t show it here.

The rest of our render components, Loading, Error, and Image are simple stateless functional components, so I won’t waste column inches on them either. However, the complete source code (as well as a live running example) can be found on CodeSandbox.

Reference implementation pattern

There’s one more technique that I’d like to point out. Whenever you use a render callback to perform your rendering﹣whether that be a Function as Child (don’t you dare!), Function as Prop (aka render prop), or component injection (what I use almost exclusively)﹣it can sometimes be advantageous to provide a default if none is specified. I refer to this as the reference implementation pattern.

This allows the consumer of your component to optionally pass in a render component. However, if they don’t, some sort of default or “reference” component will perform the rendering.

Let’s override the Loading component and make it look a bit nicer than the default reference implementation by passing the RenderLoading prop.

import GiphySearch from './GiphySearch';
import SpinLoad from './SpinLoad';
const App = () => (
<div>
<h1>Giphy Search</h1>
<GiphySearch initialQuery="dog" RenderLoading={SpinLoad} />
</div>
);

Notice that we are not overriding the RenderSearchInput, RenderImage, or RenderError components. They will use the default reference implementations.

Here’s what the SpinLoad component, built using styled-components, looks like.

import styled, { keyframes } from 'styled-components';
const rotate360 = keyframes`
from {
transform: rotate(0);
}
to {
transform: rotate(360deg);
}
`;
const Spinner = styled.div`
color: #333;
font-size: 18px;
font-family: sans-serif;
  &:before {
display: inline-block;
content: '';
width: 18px;
height: 18px;
border-radius: 50%;
border: solid 2px #ccc;
border-bottom-color: #66c;
animation: ${rotate360} 1s linear infinite;
margin-right: 6px;
vertical-align: bottom;
}
`;
const Loading = () => (
<Spinner>Loading</Spinner>
);
export default Loading;

While the reference implementation pattern may not always make sense in your component, it’s a powerful pattern to be aware of. This can aid with testing of your component, as you can inject a test implementation.

Conclusion

I hope you get the opportunity to take the disciplines and patterns that you’ve read about here today and apply them to your own code. The component folder pattern un-clutters your project, while the reference implementation pattern allows you to provide reasonable defaults to render callbacks. Both are my faithful companions when writing React applications.


Update

It was pointed out that using the component folder pattern could make it so that tabs in your editor look like this.

Notice the unfortunate side effect where a lot of tabs are named index.js. Fortunately, Joshua Comeau has a solution ﹣ if you’re using Atom anyway. He’s written a plug-in called nice-index that shows the name of the folder instead.

“We shouldn’t have to change the way we code to accommodate our tools. Get better tools.”

So now when you have multiple index tabs open, you can tell which one is which. I have it installed now and can’t believe that I’ve lived without it for so long.