Web Development

Stateful vs Stateless vs Pure React Components

January 2nd, 2020 | By Camilo Reyes | 5 min read

This article brings the battle of Stateful vs Stateless vs Pure React components to a close.

How do they differ?
When are they the most useful?

React components come in different flavors to tackle different problems. In typical programming fashion, choosing which one works best gets an “it depends.”

Each component type has its pros and cons, depending on the problem at hand. The main takeaway is knowing how each component type is useful for a given scenario.

In this take, we’ll look at the following React component types:

  • Class components (formerly ‘Stateful’ components)

  • Pure components

  • Function components (formerly ‘Stateless’ components)


To delve into each component type, we’ll use TypeScript type definitions. This aids in seeing which features are available at a high level. No prior knowledge of TypeScript is necessary.

We’ll look at what the type definition means and how it’s useful to us. Studying type definitions aids basic understanding without all the gnarly details. This will give us a peek into the internals with little effort.

Class Components

The one component type we see all over the place is the class component. It is a stateful component because it has both states and props.

This component has a ton of flexibility, which is why it is all over the place. A plus here is that this has all component lifecycle methods in their raw form. This helps tailor the component to fit specific use cases. For example, firing an Ajax request right after the component mounts.

One con is managing state manually with setState because of the complexity and increased risk.

The type definition for class components looks like this:

interface Component<P = {}, S = {}, SS = any> extends ComponentLifecycle<P, S, SS> { }


The ComponentLifecycle type definition has:

interface ComponentLifecycle<P, S, SS = any> extends NewLifecycle<P, S, SS>, DeprecatedLifecycle<P, S> {
  componentDidMount?(): void;
  shouldComponentUpdate?(nextProps: Readonly<P>, nextState: Readonly<S>, nextContext: any): boolean;
  componentWillUnmount?(): void;
  componentDidCatch?(error: Error, errorInfo: ErrorInfo): void;
}


The following lifecycle methods are of interest:

  • componentDidMount: runs right after a component is mounted, setting state here triggers re-rendering

  • shouldComponentUpdate: determines whether changes in props and state warrant re-rendering; class components return true by default

  • componentWillUnmount: runs right before a component is destroyed and can do any necessary cleanup, such as canceling network requests

  • componentDidCatch: catches exceptions from child components; unhandled exceptions unmount the component


With class components, we get all that React has to offer. One check is to see if any lifecycle methods are necessary. If not, class components are too complex for the job at hand. It is best to look at other ways to solve the problem by reducing complexity.

On the flip side, if complexity is a big concern, look into webhooks. Webhooks allow a functional paradigm while tapping into the component state.

Pure Components

Pure components have this type of definition:

class PureComponent<P = {}, S = {}, SS = any> extends Component<P, S, SS> { }


This means pure components support everything class components have, plus more. For example, a pure component does a shallow comparison in shouldComponentUpdate by default.

This optimization comes for free with pure components without any code. Unlike class components that return true by default, pure components optimize re-renders.

One gotcha is to check that props and states are not complex nested objects. Also, avoid large props and state objects, as this will affect React’s performance.

A shallow comparison goes one-level deep in props and state, for example:

{
  "item": "strict value comparison",
  "nestedObject": {
    "item": "reference comparison"
  },
  "nestedArray": ["reference comparison"]
}


For this object, the item gets a strict comparison of its value. But, nestedItem and nestedArray only get reference comparisons, or where it lives in memory.

A shallow comparison stays at the top level to remain performant. It does not drill into nested objects or arrays because it is not a value comparison. One gotcha is that shallow comparisons might skip updates when only nested properties change. This can hide bugs that are difficult to track.

Consider using pure components for one-level deep state and props. Here, we get a bit of a boost by not re-rendering when the state mutates. These components can live in tree-leaf nodes, which have simple data shapes.

For example:

class LeafItem extends React.PureComponent {
  render() {
    return (<>
      {this.props.name} {this.props.description}
    </>);
  }
}


Pure components are great for certain use cases. But what if we don’t need lifecycle methods and have complex props?

Function Components

A function component in React is defined like this:

interface FunctionComponent<P = {}> {
  (props: PropsWithChildren<P>, context?: any): ReactElement | null;
  propTypes?: WeakValidationMap<P>;
  contextTypes?: ValidationMap<any>;
  defaultProps?: Partial<P>;
  displayName?: string;
}


This component type does not have any lifecycle methods. At a minimum, it must define a JavaScript function that returns ReactElement or null.

All other properties, such as propTypes and displayName, are optional. A function component does not take in state and does not have setState. State mutation may happen in a parent component or a state machine like Redux. Function components re-render when the state mutates and does not allow optimizations. This is not a deal-breaker because React performs reconciliation.

Library internals figure out an optimal way to reconcile the virtual DOM with the one in the browser. This reconciliation process takes care of most performance concerns in React.

If performance is a big concern because reconciliation is not enough, take a look at React.memo. This is much like React.PureComponents, but for function components vs classes.

Abstracting state mutation through Redux allows function components to focus on presentation. This makes it easy to test function components in a shallow renderer like Enzyme. A shallow renderer can check all test conditions by setting props. Reducers in Redux are pure functions that are also testable. This is because pure functions have a one-to-one mapping between input and output. A plus is that all UI “logic” gets abstracted away from the presentation layer. This reduces cognitive load and aids dev creativity. Function components solve most use cases in a neat, clean way.

In simple terms, this is a function component:

const LeafItem = ({name, description}) => <>{name} {description}</>;


Conclusion

React comes in three main component types.

Class components have all the lifecycle methods to chase down specific edge cases. Pure components are built on top by optimizing re-renders with some gotchas. Finally, function components promote clean coding practices by isolating concerns.

Each alternative is a good fit, depending on the problem at hand.

Pay special attention if you're developing commercial React apps that contain sensitive logic. You can protect them against code theft, tampering, and reverse engineering by following our guide.

Jscrambler

The leader in client-side Web security. With Jscrambler, JavaScript applications become self-defensive and capable of detecting and blocking client-side attacks like Magecart.

View All Articles

Must read next

Application Security

Open Source Components and a Push for In-Depth Security

In this article, Pedro Fortuna provides his take on the recent debate about the security of using open source components. Can agility and security coexist?

December 14, 2018 | By Pedro Fortuna | 3 min read

Web Development

Data-Driven Functional Components via Ajax Featuring Webhooks and Redux

React Hooks introduced many exciting possibilities. In this post, we use Hooks along with Redux to manage application state - no need for any ugly hacks.

March 11, 2020 | By Camilo Reyes | 4 min read

Section Divider