What Early Returns/Guard Clauses are, why you should use them, and examples of how to use in React
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:
I would like to mention that Swift(a modern language published by Apple for iOS/MacOS development) even makes guard as a built-in language feature!
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?
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 —
That is, we could write components for loading
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
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
Thanks for reading :)
A note from Plain English
Did you know that we have launched a YouTube channel? Every video we make will aim to teach you something new. Check us out by clicking here, and be sure to subscribe to the channel 😎