React: Effectively composing components with async/sync options

Kier Borromeo
Aug 23, 2017 · 3 min read

Oftentimes, there’s unwarranted extra effort in building a reusable component that can be either async or sync based on the provided props.

There’s extra if-else statements around the place; it makes a lot of noise. This just makes it harder to read, all the more so change. At least, for me.

How do we build this? What if we could build these components differently? What if we could compose them instead?

Scenario

This case is common in CRUD features. Let’s take GitHub’s issue submission page as an example; the assign member widget specifically.

Sync

A GitHub repository’s issue submission page

The AssignedMembersWidget component is non-async during issue submission because everything is included within the API request when the form is submitted. It doesn’t make any AJAX request when a new user is assigned or an assigned user is removed.

Async

A GitHub repository issue

Unlike the issue submission, the AssignedMembersWidget component in this case (view / edit mode) is async. It’s disconnected in a way it makes an AJAX request when a new user is assigned or an assigned user is removed.

Code

Here’s our initial implementation for our use case. Here, we go async mode if the async prop is provided. Otherwise, it just works as usual.

The Problem

Our component is way too clever. Our component-related code hand in hand with async operations is not what we want. Async-related code distracts us from reading the component’s exact intent, line by line.

What can we do about this? This is where we should isolate our async operations.

The Solution

Instead of an if-else async-or-sync, it’s a better idea to make 2 components:

  • AssignedMembersWidget — Does what the component’s original functionality. Reacts to async props (e.g., displaying loader, etc).
  • AsyncAssignedMembersWidget— Uses AssignedMembersWidge internally. Simply manages async state.

This avoids the component from dealing with cases (in this case, showing a loader) it doesn’t necessarily have to (due to async code), and allows it to focus on state and events it’s intended to handle.

Code

Looks better now, right?

AssignedMembersWidget isn’t a component specifically for AsyncAssignedMembersWidget. We will expose it to allow other components to use it just as how AsyncAssignedMembersWidget used it, but for non-async operations.

In The Wild

react-select is a popular library implementing this pattern.

They have a separate component for the default (Select is non-async) and async (Select.Async) autocomplete component.

Select does all the autocomplete complexity on its own. Select.Async component manages all the async heavy-lifting (loading, error, etc), and uses Select internally.

Practicality

For very simple use cases like the example I provided, no. However, as you add more state, event handlers, and side-effects, you’ll eventually be deafened by the amount of noise an async operation can make.

Offloading an async request’s states to another component can help us encapsulate functionality and improve readability (bonus — testability) of our component.

Difference with Presentational and Container Components

Presentational and Container Components is a general-use pattern. It is effective on a variety of use cases. This is especially true for ad-hoc components, and in my opinion, route-specific forms commonly found in CMS software.

However, pattern presented here is specifically used to improve the code of a reusable component that can have either async or non-async option.

)

Kier Borromeo

Written by

Code monkey, mostly JS.