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:
TodoList
TodoItem
Title
DeleteButton
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.