A Rubyist’s Perspective on React

Web development started for me with PHP and Perl when table-based layouts were considered only a half-way bad idea. In 2006 I began working with Ruby and Rails, and I focussed on content management systems and e-commerce. On the front-end I have worked with plain JavaScript, jQuery, MooTools, and a variety of other frameworks. Now, a lot of buzz centers on React and multiple teams within Mavenlink have begun adoption.

The team I work with started React-based development a couple weeks ago and in that time we have learned a lot. Earlier this week I paired with one of our front-end experts, Juanca, on refactoring a component. When it comes to React my initial disposition has been pretty cynical. After pairing on this refactoring work, and taking a couple days to ruminate on what we had done, I had a moment of insight while driving my kids to see some friends: React components are just objects.

Your Functions Are Objects

At it’s most basic an object is simply a container for state that has some behavior. And, if your React components are declared like the following, then there is nothing surprising about this observation:

class Button extends React.Component {
render() {
return <button>{ this.props.label }</button>;
}
}

But, what about this declaration style?

function Button(props) {
return <button>{ props.label }</label>
}

I’m definitely “sorry; not sorry” to let all the functional purists out there know that you just have an object there. Sure, it is invoked as a function and can be reasoned about as a function. However, it is actually more sensible to reason about it as an object. By making that cognitive shift decisions about where and how to refactor become easier. At this point the only thing obviously missing is some behavior.

A Refactoring Example

To make my point a little clearer, let’s start with a modest component declared in the functional style:

function Button(props) {
return (
<button onClick={props.clickHandler}>{ props.label }</button>
);
}

The onClick implementation here is pretty straightforward. Now, let us imagine a need to pass an option back to the clickHandler. We have some different ways this could be done. The most straightforward would be to add chain onto props.clickHandler with bind(null, props.target). However, as complexity grows this is not likely a great pattern. And, even in this simplistic case the code gets harder to parse. Instead of this approach, we can take advantage of the local scoping within our Button function like the following:

function Button(props) {
function clickHandler() { props.clickHandler(props.target); }
return <button onClick={clickHandler}>{ props.label }</button>;
}

What we end up with is no different than a class with a private clickHandler method. With this approach the code is easier to read, and easier to modify. In the end all we have is an object with a singular public interface: its constructor. This way of thinking about React components can lead towards many of the same kinds of approaches that are embodied by SOLID object-oriented design. Additionally, the testing approach almost automatically focusses on behavior and resists attempts to pry too much about implementation.

Final Thoughts

More could be teased out regarding using this approach in reasoning about React components. I think value is likely to come from three main areas as this approach is applied. First, increased clarity about how and what to test should emerge when components are viewed as boxes of behavior, which should also bring with it better test isolation. Second, refactoring paths can be guided by patterns that are already available, and well understood. Third, engineers who have depth of experience with OOP can make more meaningful contributions when the prevailing pretensions get rejected out of hand. After all, front-end web development looks to me an awful lot like desktop development, except that this generation is not doing well learning from the lessons of that historical analog.