The zenith of React’s functional paradigms — Part 2: The worst parts of React

Adam Terlson
ITNEXT
Published in
3 min readJun 16, 2019

--

Photo by Benjaminrobyn Jespersen on Unsplash

The two main building blocks of a functional React codebase — stateless functional components and higher order components — both have serious flaws.

The limits of Stateless Functional Components

I have published my love of SFCs before. However, there were a couple reasons I couldn’t leverage them as much as I would have liked.

In React Native, the need for performance optimization isn’t a question of if, but when—eliminating unnecessary re-renders is “React Optimization 101”.

The benchmarks don’t lie: SFCs were not the fastest option. The user of especially a React Native app could possibly suffer real perceptible performance differences simply because the engineer chose to leverage a function instead of a class.

This is not how the world should work!

Engineers had no choice: use PureComponent (or wrap every component with an HOC, which would in turn itself use PureComponent).

HOC component composition sucks

HOCs are a solid choice to add functionality to your React components. Need to access state, transform props, access context, dispatch an action, or anything else you can imagine, HOCs can be how these behaviors are described (see the popularity of libraries like recompose).

However, taken too far, what you’re left with at the end can be a bit of a mess:

const FinalComponent = compose(
connect(...)
mapProps(...),
dispatchOnMount(...),
defaultProps(...),
...20 HOCs later...,
pure,
)(IncompleteComponent)

Beyond simply being a PITA to trace/debug, achieving behavioral definition via component composition has some serious flaws that aren’t so in-your-face.

Problem 1: Prop & render marshaling

Because props must literally flow from one component to the next, it’s the responsibility of each HOC to make sure they make it through, unmodified and uninterrupted.

compose(
withState,
myCustomHOC, // Must pass all state changes through, uninterrupted
)(MyComponent)

Beyond simply being a new class of error no one wants in their life, it also means it is practically impossible to apply a shouldComponentUpdate in an HOC — after all, preventing the HOC from re-rendering will prevent components downstream from receiving prop changes from upstream.

Problem 2: All-or-nothing type safety on props

In a perfect world, when writing a render method with JSX, the type system will ensure that all prop requirements of the component you’re using will be satisfied (and hopefully have working intellisense).

const MyComponent = compose(
someHOCA, // Requires prop "propA"
someHOCB, // Requires prop "propB"
)((props: { propC: string }) => (...))

<MyComponent propA={10} propB={false} /> // Error - PropC is missing

However, in order to achieve this with HOC component composition, the type system must be able to fully understand every HOC and what effect it has, if any, on props.

const MyComponent = compose(
someHOCA, // Requires prop "propA"
someHOCB, // Requires prop "propB"
propMap(props => ({ ...props, propC: "Yes!" }), // Add propC
)((props: { propC: string }) => (...))

<MyComponent propA={10} propB={false} /> // No error (hopefully)!

This complexity (see: overhead) creates a lot of problems when trying to create a React project with good type coverage, and good (see: functional) style.

When it comes to types, it’s all or nothing. Life or death. Kill or be kil — you get the point.

If you missed it, go check out part 1 for more on the history of React’s functional paradigms.

Part 3: Soon

I will discuss how these problems have been solved, and what I see as the idiomatic way forward for the functional react codebase.

--

--

A passionate software engineer from the US. Loves building things. Bad at social media.