One of the most common disagreements I see in React has to do with whether to use class or functional components. There are certainly merits to both approaches, but over the last few months, I’ve definitely come to favor one over the other.
I’ve discovered that when I write pure functional components I tend to write cleaner code. I’ve also found I have a much easier time returning to that code, figuring out what I was doing, and making changes to the behavior of the system.
Before I embark on this
religious crusade enlightened debate, I want to clarify what I’m advocating. I’m not saying classes shouldn’t be used to create React components. They can, and perhaps should be, used to create certain components requiring functionality pure functions can’t provide (no pun intended). Instead, I’m encouraging developers not to only use classes. It’s tempting to “simplify” the code base by just making everything a class. But, as I’ll try to demonstrate below, it actually makes things more complex and harder to reason about.
So. now that that’s out of the way…
Let’s refresh, what do I mean by “functional” versus “class”?
Here I’m referring to two different ways of accomplishing the same thing: declaring a React component.
So, if they’re accomplishing the same thing, why prefer functional syntax?
Functional syntax encourages smaller, focused components
Components written in a functional way give us access to a really concise interface devoid of boilerplate. You can use object destructuring to pull out any props you need. There are no lifecycle methods and no state. You often don’t need an explicit return, allowing you to write really small, focused components.
Sure, you can bloat them up but the syntax discourages that. Want to do some calculations before your return statement? You can, but you have to swap the parenthesis wrapping your arrow function with curly braces. As you continue extending the component with markup the sheer size and responsibility of it quickly becomes clear.
With a large class component, you can have a component that’s not doing a lot in spite of its size. Constructors, state management, lifecycle methods, and the class syntax itself all work together to hide the amount of work the component is doing and the number of responsibilities it has.
Not only are functional components harder to load with bloat, they also offer a nice incentive to breakout new components: it’s really easy. A simpler import and a tight syntax make creating new components a breeze. And, with each new one you create, you reduce the cognitive load required to understand the component you’re working on.
They’re easier to test
Functional components tend to be easier to test for a number of reasons. That alone makes my life much easier.
As we talked about above, they’ll tend to be smaller and have few responsibilities (hopefully just one). That’s great! I test for successful rendering, the appearing of key elements, and that props are used as I expect them to be. Then, I’m confident my component works as intended. If I have a class, I’m going to have to be very careful when designing the test. Even if it doesn’t have state, lifecycle methods, or functions I have to carefully check for each. That’s a fair amount of cognitive overhead for something that doesn’t exist.
If you practice TDD (and I encourage you to try it if you don’t), functional components fit nicely in the 30-second cycle of back-and-forth between writing failing tests and making them pass. You’ll find you have to write less code to get to a place where your tests pass. It might not seem like much, but when writing tests drive your development the little things add up in a big way.
They communicate a clearer interface
I think this point isn’t talked about enough. React components are functions and classes like any other domain. It’s easy to forget when we’re looking at a component full of JSX that it’s not HTML we’re looking at, but an object we’re designing that’s exposing an interface to other objects. In React, that interface takes the form of props – primarily, anyway.
Take a look at how different the expression of the interface is between the styles:
When we use a class syntax to define our component, the interface (props) is hidden within our render function. When we use a functional syntax we can take advantage of destructuring at the very top of our component, inside our function’s signature – which is exactly where it should be. You can look at the component and immediately know what props you need to pass in to make it work.
They’re much less noisy
Signal-to-noise is real. If you have a small five line component, a class will have more boilerplate than actual component. Functions, on the other hand, have hardly any noise at all. Even the declaration line is loaded with useful information, such as the props mentioned above. In fact, in that last example, literally most of the class-defined component was made of boilerplate.
To be clear, there’s nothing wrong with class components. They work just fine and are sometimes necessary. The problem is they offer no constraints. There’s nothing hard about making them do too much. I’m sure a well-disciplined team with a rigorous code review process can prefer them and still write generally clean code. But they do make it easy to stray from best practices and developers, myself included, tend to do what’s easy.
That’s why I help myself by making it just a little harder to write messy code. It’s not a silver bullet, but it can help a great deal.
What do you think? Do you tend to prefer one style over the other? I’m curious if there are any points for classes or functions I neglected to mention here.
As always, thanks for reading.