Skip to content

Early Returns/Guard Clauses in JavaScript (and React)

2020-05-25

by Thor  Chen

In this article, we will go through the following sections:

  • The basic concept of Early Return
  • The reasons for using Early Return
  • Using Early Return with React
  • Using Early Return with Hooks

Now, let’s get started.

What is Early Return?

Early Return is a pattern that suggests us to avoid nested if-else statements by checking the preconditions and return or throw as early as possible. Usually, Early Return is also called Guard Clause or Bouncer Pattern.

A basic made-up example of this pattern is turning the code below

 

… to something like this:

The reasons to use Early Return

 

1. It keeps our code readable and understandable

Simply looking at the example above, the Early Return version is visually flat and it is much easier to read and understand than the deeply nested version.

2. It offloads the burden in our mind when dealing with complex conditions

Imagine (or recall) a case where we have a lot of nested if-else branches in the function, how often would we get lost while writing the logic?

In contrast, if we can get rid of invalid and special cases as early as possible, we will be able to focus on the “real” main body of the function with peace in our mind.

How do we use Early Return in React?

In React, Component is the boundary to compose our UI building blocks, and it is easy to apply Early Return in this level. For example, we can return a Spinner when the data is loading, return an Alert when there is an error, return some messages when data is empty, and return the “real” presentational element when data is ready.

 

What about hooks?

MyComponent in the code above is a presentational component, and we can just put useData hook (imaging we have this custom hook somewhere in the codebase) inside it to let the component load the data by itself:

 

However, there is a very important and frustrating thing needs to be aware of — we can’t return early if we are intended to call a hook in some cases after the return statement. The reason is that React requires us to call hooks in the same order each time when a component renders.

That is to say, the code below is NOT valid and React will log errors and warnings for you, because the useData2 hook and React.useMemo hook are not always getting called — the order of calling hooks is changing.

You may get errors and warnings such as: “React has detected a change in the order of Hooks called by XXX” and “Rendered more hooks than during the previous render”

An immediate solution is to move the null checking inside the hooks:

Apparently, it is really annoying to have null checks spread everywhere, and the case becomes much worse when we have many data to be loaded while they are relying on each other and some of them need to be called conditionally (I do encounter this situation when using data from multiple sources and aggregating them to build reports).

A maintainable approach to solve this is to use our good old friend — render props.

That is, we could write components for loading data1 and data2 respectively. These components render nothing when the data is not ready, and they call children function to render the actual dom when data is prepared. I call these components as DataGuard.

Once we have these DataGuards, we are able to compose them like this:

 

 

By doing so, we do not need to worry about spreading and duplicating null checks everywhere anymore, and these DataGuard components are highly reusable and composable — they are just there to provide data for their children and there is nothing to do with DOM emit, so that they can be used anywhere needs the data, regardless how the data should be presented.

An interesting observation is that we start to see the pyramid of nesting components when using DataGuard components. I think it is perfectly fine as nesting components in a tree is exactly the way how React works — If we just ask ourselves: how do we use a component? The answer may simply become “we put it inside another component”. It is true all the way up to the root component — the one being passed in ReactDOM.render function.