How to name and organize your React components
When I first began building complex React applications, I struggled to
determine the best way to name and organize my components within my
codebase. Having come from the Rails school of convention over
configuration,
I was perplexed to find that most modern JavaScript apps rely heavily on
custom configuration and don't adhere to any sort of community-driven
conventional norms. That's changing slowly with the advent of toolkits
like
create-react-app,
but even its conventions go right out the window as soon as you npm
eject.
After a couple years of learning and mistakes, there are a few guidelines I use when organizing my React components so my code is more readable, understandable, and succinct.
Compose your components into smaller subcomponents
While it's tempting to just keep adding bits and pieces to your
component's render method, this can grow to the point where it becomes
difficult for new eyes to discern your intentions. Imagine a render
method like this:
render() {
return (
<div className="todo-list">
<div className="todo-list__items">
{this.props.items.map(item => (
<div className="todo-item">
{item.title}
</div>
))}
</div>
</div>
);
}
Instead of rendering todo items within the root component's render method,
create a new component:
const TodoItem = props => (
<div className="todo-item">
{props.item.title}
</div>
);
Then, call it in our root component's render method:
render() {
return (
<div className="todo-list">
<div className="todo-list__items">
{this.props.items.map(item => (
<TodoItem item={item} />
))}
</div>
</div>
);
}
It's a subtle change, but making these sorts of changes proactively can keep your components readable. And becuase React only re-renders those components that change, extracting smaller components can improve your application's performance.
Extract iterators into class methods or new components
Extracting a subcomponent is a good first step. But
we can go one step further by extracting the map iterator
call into its own method in the root component. This improves
the readability of the render method:
render() {
return (
<div className="todo-list">
<div className="todo-list__items">
{this.renderItems()}
</div>
</div>
);
}
renderItems() {
return this.props.items.map(item => (
<TodoItem item={item} />
));
}
Now when we scan the render method, we see a more succinct
summary of its contents!
Only use ES6-style class components where state is needed
Notice in my previous example that I've opted to use const to
define the TodoItem component. This is because, in its current
incarnation, the TodoItem component is stateless.
In React, stateless components are merely functions that return
a React-wrapped DOM element. Unlike using the ES6 class keyword or the
now-antiquated React.createClass method, stateless components cannot
hold their own component state in a this.state object.
The reason this syntax is favorable is because it encourages authoring
code in a functional style. Let's expand our TodoItem component to
include a "Delete" button:
const TodoItem = props => (
<div className="todo-item">
<div className="todo-item__title">
{props.item.title}
</div>
<button onClick={props.onDelete}>
Delete
</button>
</div>
);
Here we've enclosed the title in a new container div, and then
added a <button> tag with an onClick handler set to a hypothetical
onDelete thunk handler that we would pass through from the parent
component.
Just like in our iterator extraction example above, there's opportunity to make this component more readable. However, because this component is stateless we can use a more functional style:
const Title = props => (
<div className="todo-item__title">
{props.value}
</div>
);
const DeleteButton = props => (
<button onClick={props.onDelete}>
Delete
</button>
);
const TodoItem = props => (
<div className="todo-item">
<Title value={props.item.title} /> <DeleteButton
<DeleteButton onClick={props.onDelete} />
</div>
);
Our goal in doing these extractions is to reduce fatigue when scanning your components' code. New developers who visit this code for the first time will be greeted with components whose markup is only a few lines, making it much easier to parse and understand than if they were tens or hundreds of lines.
Keeping this habit early on will mean your codebase can grow and remain understandable to newcomers.
Organize composed components into subdirectories
So far, we've built the following components:
TodoListTodoItemTitleDeleteButton
As our codebase grows, the way we physically organize our code on disk is going to become more critical. I've experimented with a couple methodologies.
The first is to create a directory called components, dump all your
components in there, and call it a day. This is fine for projects with
fewer than 20 or so components, but it becomes cumbersome as the number of
components grows.
Instead, I've settled on creating subdirectories for certain root-level components. In our example, we could envision the following directory structure:
components/
TodoList/
index.js
Item/
index.js
Title.js
DeleteButton.js
Organizing our components in this way has the following benefits:
- Components only ever reference other components within their own subdirectory.
- We can name components based on their context. For instance, instead
of naming our item component
TodoItem, we can call itItem. This reduces unnecessary verbosity. - Our components become portable. By encapsulating their hierarchy within a single directory, we can reuse the component in other codebases easily.