Reactjs: Class Components and Functional Components
--
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
UPDATE: This article was written before React 16.8 and the release of hooks. I’m choosing to leave the article live in hopes it can help those working with legacy code, or anybody who just wants a primer contrasting the basics of class-based and functional components. I’m not going to write my own intro to hooks as there are already several great rundowns by others, and the official docs are great.— LB
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Anyone who spends time studying React code examples from around the web will quickly face several ways to define components. Knowing the difference between each and when to use one versus the other is not always obvious at first, so let’s take a quick look at class components and functional components, and the various ways they are created.
Class Components
If you’ve been running with React since the olden days (well, 2015-ish), this may be the component format you are most immediately familiar with. Using React’s createClass factory method, a literal is passed in defining the methods of your new component. The only required method is render.
Easy-peasy, and with its own unique benefits to boot, but arguably more “secret sauce” than any of the other techniques we’ll explore. The good news is that you could live your life using just this format. The bad news is that, increasingly, that’s a sub-optimal choice.
Along with the rise of ES6, React v0.13 introduced a new way to define class components that leveraged the new class keyword and module syntax. This reinforces the “React is just javascript” message that was somewhat obscured with the factory approach. The modern, ES6 pattern looks something like this:
How they’re similar
All told, these two styles of creating components result in fairly equivalent returns (we’ll get to the differences in a minute). While the render function in the examples above is the only required method in both cases, because these components are classes, we are free to include other methods within our class object: both our own arbitrary functions (think handleClick, or formatText) and a handful that are given special significance. It’s probably fair to say that the most important of these special methods are the lifecycle functions, such as componentWillMount, componentDidUpdate and their brethren. The React docs give a great overview of the entire family, but all told the lifecycle methods allow us plenty of ways to tie our own behavior into each of the steps our component will pass through when being rendered, updated and removed.
The other headline feature of class components is that they are capable of maintaining their own internal state. If you are working with Flux/Redux, you might eschew this option in favor of an outside store to hold your application state. However, for vanilla React apps or in specific cases, it can still be useful to let state be maintained within the component context where it’s used.
How they’re different
But these two methods of creating class components aren’t entirely equivalent. In the move toward a more standard approach, the ES6 class based method gives up a couple of niceties that were packed into classes generated with React.createClass.
Let’s take a look at how you might find yourself setting up your class in each of the two approaches:
Using the React Factory Method
Extending the React Component Class
Most immediately, there is a significant difference when it comes to defining state and default props. The first example, built with the createClass factory, comes with handy getInitialState and getDefaultProps methods that simply require objects to be returned that will then be available elsewhere within the class as this.state and this.props.
On the other hand, things get a little more interesting in the second example, built as an ES6 class. First we have our initial state, which is an assignment within a constructor function. That’s a bit different, but not a particularly thorny change. Slightly more onerous are your default props, which are now defined via a defaultProps attribute. Though you’ve got a few options if you are rolling ES7, the most common pattern is to assign defaultProps outside of your class. If you are setting up propTypes, you’ll find the same differences apply there as well.
There are other important changes that are demonstrated in the constructor function in the second example. The first is the call to super(props), which passes your incoming props up the inheritance chain and calls the constructor of the parent Component class. Calling super in the constructor is important, as you’re overwriting the inherited constructor with your own when you add your setup. Passing props is a best practice because it makes props available elsewhere in the constructor, though it isn’t strictly needed if you aren’t touching props until later in your class. Still, it’s easy to miss if you or another developer are adding to your constructor later, so it’s good practice to consistently provide props.
To round out the example constructor, we set our myFunction to bind this, ensuring that the context can be accessed in the method we’ve defined. Context management in javascript is something that no sane person enjoys, and the good news is that React largely handles it for us when it comes to methods it knows about, like the lifecycle methods. When setting your own methods, it still requires a nudge, and so we set it up from the constructor, using .bind(this). Speaking of the lifecycle methods, we’ve ignored them here in our examples for sake of simplicity and because setting them up isn’t substantively different between the two approaches.
Class components give you a lot of room to tap into the magic of React and build upon the framework it provides. But they aren’t the best choice for every situation. With great power comes greater complexity, and so sometimes it’s appropriate to reach for something a bit more svelte.
Functional Components
React 0.14 delivered a new shorthand for creating simple stateless components. These are often known as Functional Components because they’re just regular old pure javascript functions that return React elements. In its simplest form, a functional component looks like
This is beautifully compact and “pure” in the sense that the same input always yields the same output. A component formated like this cuts much of the cruft associated with class components, and in this form can rely on implicit return, cutting the whole thing down to just a few lines. There is a slightly beefier version that defines return explicitly and allows you to add your own arbitrary methods:
However, this terseness comes at a cost. This function will be consumed and rendered by React, but it’s not constructed with or extended from React, and therefore doesn’t get to inherit all of goodies of a React class component that we’re used to. Functional components lack the ability to maintain their own state, and they get none of React’s lifecycle methods.
This, of course, means that functional components aren’t appropriate for every use case. However, it’s often worth examining how you are architecting a growing React project so that you can use as many functional components as possible. Even in existing apps, it’s often possible to start at the tips of the “branches” of your component tree and work backwards treating many components as purely presentational and moving logic toward the center.
And the winner is! Well, That depends…
Now that we’ve seen the available formats that your React components can take, which format should you use? There is no simple answer that can be applied 100% of the time. Like any other situation, generally less code wins. That would point us toward functional components, and when you can get by without state and lifecycle, functional components should be the first format you reach for. Not only is the “signal to noise” ratio better when looking at functional components, organizing your React app to use functional components generally discourages antipatterns, like holding state in too many places at once.
But of course, it’s never that easy, and there are still many cases where class component remain appropriate. I find it’s often best to start new components as simple implicit functional components and then “upgrade” them as needed.
Once a component is ready to graduate into a full-fledged class component, I would suggest reaching for the ES6 class format rather than the older createClass format. Facebook has stated that they plan to eventually deprecate the old format.