Composing React Components

Dorian Smiley
Brainly Technology Blog
3 min readAug 6, 2021

--

How to avoid the Wrong Abstraction using Wet Code

Abstract Tree
Photo by Jr Korpa on Unsplash

Back in 2016, Sandi Metz wrote a fascinating article entitled “The Wrong Abstraction.” The article is a great primer for React developers and why they should avoid using conditional rendering to build component variants. One quote from this article makes the argument as to why: “The code no longer represents a single, common abstraction, but has instead become a condition-laden procedure which interweaves a number of vaguely associated ideas. It is hard to understand and easy to break.” This is the essence of the wrong abstraction. If you use feature flags or other config settings as an abstraction over your React components, you need to stop. Your code will eventually become impossible to reason about or test (consider 2^n permutations where n is the number of toggles).

So what is the alternative? Dan Abramov gave a wonderful talk in 2019 at Deconstruct, where he outlined how Facebook thinks about these problems. In it, he makes a case for copying code over abstraction. As it turns out, sometimes, you need to repeat yourself!

Today, I’d like to share how we are tackling this problem at Brainly. We’ve implemented a system that combines Higher-Order Components (HOCs) with Inversify to deliver component variants across ten markets. When we encounter a use case that requires a component variant, we open the component for an extension. This is achieved by moving all component logic into a HOC, for example:

In this example, we removed all the logic in our original component and placed it in the HOC. Anything the component needs to render is passed as properties. This is important because it allows subsequent HOCs to access the functionality of previous HOCs (similar to super) and to override their behavior (polymorphism for functional components). For example, a stateful version that increments the like count on click:

Here the handle click function is overridden, but the behavior assigned by the parent HOC is still executed with `props.handleClick(e);` on line six.

The next step is to export the base component:

Here we’ve reduced the component to a static render function. At Brainly, we feel this reduces the risk of copying bugs to nearly zero. Furthermore, with our base button exported, we can now extend it using ramda compose.

Using compose allows us to mixin the core logic while changing the behavior to use our new context and increment the count on click. The render function is mostly the same but customized for the market. Merging the styles using object destructing is an important aspect of this solution. Yet another reason why you should be using CSS modules!

To use this component, we don’t want to copy the entire component hierarchy. So we instead use Dependency Injection (DI) with Inversify.

The customized component is then injected into our page and rendered using a slot pattern.

The benefits of this approach at Brainly are enormous. First, we reduce the blast radius of variants to a single market. Second, we know exactly what code was deployed to a given market without speculating about the effect of config params. Further, we can audit all source code for a particular market, and we have better replay capability for builds. Lastly, and most importantly, we’ve eliminated wrong abstractions that all too often slow us down due to “big ball of goo” problems. Happy coding!

--

--

Dorian Smiley
Brainly Technology Blog

I’m an early to mid stage start up warrior with a passion for scaling great ideas. The great loves of my life are my wife, my daughter, and surfing!