React/Redux TodoMVC, Part 6: Refactoring
• CommentsToday we’re going to start breaking up that huge Main
component. Yes, I said “huge” - in the React world, smaller components are more normal. React encourages the creation of small, reusable components, which are then composed together.
There’s three big parts to the Main
component: a header, a main part, and a footer:
<section className="todoapp">
<header className="header">
...
</header>
<section className="main">
...
</section>
<footer className="footer">
...
</footer>
</section>
Since I already have a Main
component (oops), at the end of this post I’m going to rename my Main
component to be TodoApp
instead. My goal here is consistency: I’m going to break up TodoApp
into Header
, Main
, and Footer
, in a way that the class
of each HTML element will match the name of its React component. This just results in more maintainable code.
If I had more control over the HTML output, I could choose to change the class
for the main section from main
to something like todos
, and keep my old Main
component as it is.
However, in this case, the TodoMVC app should have fairly specific HTML output, to enable things like automated acceptance testing.
So, I have to rename my old Main
component because I didn’t think far enough ahead. Sorry about that. I am totally making this up as I go along…
Breaking Up is Easy to Do
Let’s break out the child components first. In React, a common pattern is to use a components
subdirectory to hold all the components. In larger apps, there can be further subdirectories underneath this, but this app is small enough that all the components should fit. I think. :)
I’m going to (temporarily) call that middle child Todos
instead of Main
(I’ll fix this in the next step). The Todos
ends up looking like this (in components/todos.jsx
):
import React from 'react';
export default function Todos() {
// This section should be hidden by default and shown when there are todos
return (
<section className="main">
(copied from main.jsx)
</section>
);
}
Well, that was pretty easy… The Footer
follows the exact same pattern (exposing a Footer
component from components/footer.jsx
).
The header is a bit more difficult, because we have the dispatch
method coming in and used in the header. There are three common ways to approach this.
- For those who like their dumb components to be really dumb, then the
dispatch
method is only connected to the smart component parent (in this case,Main
).Main
will then binddispatch
to the action creator (TodoActions.add
), resulting in a new function. That function is then passed down to the dumb component, which uses it to respond to the user action. - For those who like their dumb components to be somewhat dumb, then the
dispatch
method is only connected to the smart component parent (Main
).Main
will then passdispatch
down to the dumb component, and the dumb component calls the action creator (TodoActions.add
) and passes the resulting action to thedispatch
it got from its parent. - For those who like their dumb components to be only a little dumb, then the
dispatch
method is connected to the dumb component directly. The dumb component calls the action creator (TodoActions.add
) and passes the resulting action to its owndispatch
.
You could argue that the first option is the “most pure”, and the last one is “least pure”. While there are advantages to purity (namely, reusability), in my (limited) experience I feel that the repetitive boilerplate required by the purer approaches outweighs their advantages. For this reason, I take the third approach and just connect the dumb component so it gets its own dispatch
directly.
Applying this to the Header
yields this (in components/header.jsx
):
import React from 'react';
import { connect } from 'react-redux';
import TodoActions from '../actions/todoActions';
function Header({dispatch}) {
return (
<header className="header">
<h1>todos</h1>
<input className="new-todo" placeholder="What needs to be done?" autoFocus onBlur={e => dispatch(TodoActions.add(e.target.value))}/>
</header>
);
}
export default connect()(Header);
As you can see, I’m connecting the Header
component, so that it gets dispatch
, which it then uses directly to dispatch the ADD_TODO
action at the appropriate time.
Now that all the components are broken out, Main
is quite simple (in main.jsx
):
function Main() {
return (
<section className="todoapp">
<Header/>
<Todos/>
<Footer/>
</section>
);
}
Renaming
Finally, I have to do a bit of cleanup, because I chose a poor name (Main
) without thinking of my HTML structure first.
So, I’m going to rename the old Main
to TodoApp
instead (and also change the file name from main.jsx
to todoApp.jsx
), and then rename Todos
to Main
(and change the file name from components/todos.jsx
to components/main.jsx
).
Now the component naming scheme is more maintainable: each part’s class
attribute matches the component name and the file name in which that component lives.
Source code at this revision - Live site at this revision (ignore the “startup flicker”; that’s just due to the way it’s hosted)