When should I start using React Hooks?

Reading Time: 5 minutes

Should you start using React Hooks today?
Only to write new components, not to refactor old ones.
Why write components with Hooks?
1. Separate (and isolated) concerns.
2. Avoid juggling HOCs, render props, children as functions, and classes.
3. Avoid duplicate logic between lifecycle methods and components.

React recently pushed v16.8 to introduce the long awaited Hooks API. It was Alpha released with v16.7 and is now stable for production. It introduces a new way to conceptualize and write components as functions. This post outlines 3 main reasons to adopt Hooks for your frontend components.

Should you start using React Hooks now?

To refactor existing components? No.

In my attempt to refactor Confluence's Blog Tree component, I learned that it's not worth refactoring an existing component with Hooks, because:

  • Most verticals have tight deadlines and higher priorities.
  • There are many complex existing components. It is not pragmatic to go on a refactoring witch hunt.
  • Refactoring can lead to a 1-1 mapping, which may not take advantage of the Hooks paradigm.

To write new components? Yes!

Designing a component with Hooks allows us to separate concerns from day 1. It helps us produce deterministic components (that are based on pure functions) by isolating side effects.

Why adopt Hooks?

Code examples below are simplified for clarity

There are several reasons to adopt Hooks. These are the top 3 that I've witnessed:

  1. Separate (and isolated) concerns.
  2. Avoid juggling between HOCs, render props, children as functions, and classes.
  3. Avoid duplicated logic between lifecycle methods and components.

1. Separate (and isolated) concerns.

In Class components, data concerns tend to get coupled inside lifecycle methods. This creates components that are difficult to understand, refactor, and test reliably.

In the Class example below (left), notice that the data-fetching, internal data-manipulation, and the DOM manipulation are all chucked inside componentDidMount. However, they live and get invoked separately in the Hooks implementation allowing us to cleanly isolate different aspects of the component.

With the Hooks implementation (right), it is also easier to extract the isolated effects and their corresponding states to custom Hooks that can be reused in another component (e.g. PageTree).

2. Avoid juggling HOCs, render props, children as functions, and classes.

Do you remember React Mixins? Well, there's a reason we're trying to forget it. However, Mixins led to the creation of HOCs, render props, and children as functions. While these alternatives work well for different use cases of reusability, they introduce complex patterns in our React components.

For example, the Class implementation below (left) nests two Context Consumers to create a false sense of hierarchy in the AdminBannerComponent. The ThemeContextConsumer has no data-dependency on the SessionContextConsumer. When more Context Consumers are added (e.g. ApolloConsumer, PageContext, etc.) the nesting becomes large, harder to understand, and less manageable.

In the Confluence Frontend codebase, we constantly juggle between HOCs, children as functions, classes, and plain JS functions.

Hooks allow us to replace several paradigms with just functions as seen on the right below.

This simplifies our components by making them flatter (no unnecessary nesting) and more explicit (clear data source). Notice the difference in clarity between the two styles of component composition.

3. Avoid duplicated logic between lifecycle methods and components.

Duplicate logic convolutes components. This happens a lot inside componentDidMount and componentDidUpdate, mainly because lifecycle methods are not flexible or reusable.

With Hooks, the concept of "lifecycle methods" disappears. Now, functions (useEffect or custom Hooks) will be executed on every "render cycle", but can also be declaratively invoked only when certain values change.

In the Class example below (left), loadPages is invoked when the component mounts and again when the component updates. The logic to load pages (and its corresponding performance events) are duplicated between componentDidMount and componentDidUpdate. Furthermore, a check to determine whether the currentPageId has changed is necessary to avoid unnecessarily loading pages. This is imperative and inflexible.

In the Hooks counterpart (right), loadPages is executed more declaratively. It is invoked on render cycles when the currentPageId changes, but a manual check is not necessary.

Similarly, subscribing and unsubscribing logic can now live together instead of being dispersed in componentDidMount and componentDidUpdate. useEffect allows clean up logic if a function is returned.

Additionally, both invocations of useEffect below can be extracted into their own Hooks to be reused in other components that require loading pages and subscribing to newly added pages.

How to get started with writing Hooks

Most of the info mentioned below can be found on React's Hooks documentation ยป

Upgrade your react packages to v16.8.0

Hooks have been released for production in v16.8.0. All react packages need to be upgraded in order to consume Hooks according to the Hooks FAQ.

How to use the state and effect Hooks

The first two Hooks that will allow a functional component to incorporate state and perform async/impure actions are useState and useEffect. Learning how to use them helps understand when a Hook gets executed during a component's render cycle, and further allows for creation of custom Hooks.

Rules of Hooks

The Rules of Hooks outline and explain a couple of best practices while using Hooks. They also mention the Hooks ESLint plugin that can help enforce those rules.

How to write custom Hooks

The React documentation on custom Hooks does a good job identifying the need for a custom Hook, and then extracting the re-usable logic into a separate function. This is a nice place to start writing custom Hooks.

Community maintained Hooks

Hooks written and shared by the React community can prevent reinventing the wheel. Frequently used Hooks like useEventListener or useLocalStorage have already been written!

I hope this blog post helps you understand the differences between a traditional and a Hook-based component, and is a starting point for adopting Hooks.

If you have any feedback or questions, feel free to let us know on the Atlassian Developer Community, or on twitter @atlassiandev.