Pattern for Encapsulating Stateful Front-end Logic with React Hooks with Cross Repo Support

Hooks are a powerful tool that can be used to solve the difficulty of encapsulating and sharing stateful logic across apps. It can be used as an alternative to Redux. This article explains how hooks can be used to achieve clean encapsulation and it comes with patterns and examples to aid adoption.

The last part of the article will explain a brand new approach to build front end application that is centered around context instead of global Redux store.

Encapsulation Problem with Redux

I made Figure 1 to demonstrate the daunting encapsulation scheme for Redux. There are simply too many things that is required to encapsulate and glue the stateful logic.

Figure 1: Redux Encapsulation of Stateful Logic

In the case above, what we need ideally is encapsulation of the isOpen stateful logic. The stateful logic should have a single API that contains getIsOpen and toggleIsOpen. The API can have multiple implementation that differs in where isOpen is stored or what side effect is called but not the API, thus decoupling how we use the API from the implementation details of the API. See Figure 2.

Figure 2: Idealistic Encapsulation of Stateful Logic

This article explains how to use Hook to deliver the idealistic encapsulation of stateful logic. The remainder of the article is going to explain the approach step by step.

Introduction to React Hooks

A Hook is a function that calls the React hook API directly/indirectly. Hooks are customary prefixed with “use”. Hooks can only be executed in a function component at the top level, though you can pass hook as parameter anywhere. Below is a basic hook example:

Example 1: Basic hook

Any function that returns React.Node can be used as a Function Component. Hooks that return React.Node are also a Function Component.

Now it is time to do some exercise:

Exercise 1: Draw the Venn diagram for hook and function. Draw the Venn diagram for hook and Function Component.

Exercise 2: Should you write Class Component if you can solve with Function Component? Should you write a hook if you can solve it with a function? Should you write a component if you can solve it with a hook?

Answer for both are at the end of article.

Use React Hooks to Encapsulate Stateful Logic

If we move a single boolean from state to context, it will merely be replacing one hook that call useState() inside to another hook that call useContext to use the state in the context. Both hook will return the same API that is part of the encapsulated interface.

Figure 3: React Hook Stateful Logic Implementation vs Redux

Even better, when using React Hooks based stateful logic, if the related context provider is absent, the logic can be designed to implement some default behavior, thus allowing you to build stateful logic into UI components that is backwards compatible with current generation of UI components that is not context aware.

For example, Material UI can be designed to automatically use Google user account configuration to change its look, feel and behavior if specific context provider existed, but will still render the default Material UI if Google user account configuration is not available in the required Context. Without this capability, Material UI will need to have various props that allow customization and each app needs to have its own logic that reads Google user account configuration and use that to map to a Material UI pro that customize look, feel and behavior. Having the option to publish customization logic in open source library easily due to superb encapsulation is not available in Redux.

The new capability is not limited to customization. Any stateful logic can be released via open source library with a default behavior in the case the required context provider doesn’t exist.

Build Apps around API

The API is a replacement for selector and action creator in Redux. The API bundles related selectors and action creators together and passes them between components. Compared with Redux’s method of using mapStateToProps and mapDispatchToProps, API-based dependency injection provides a proper reusable abstraction instead of typical Redux practice of bundling unrelated selectors together, and bundling unrelated action creators together.

The example below is a block diagram that describes two stateful logic blocks. The MessageProvider provide multiple TargetingApi that will trigger Modal, Popout or Banner on screen. The TargetingApi is consumed and again provided by StateMachineProvider to sequence through a series of Tooltips if user is targeted for a specific message. Now we have a single Targeting API defined once and used in many places.

Figure 4: Example for Building Apps around API

Show me the Code

Example 2: Encapsulated MessageProvider Stateful logic consuming messageApi and providing targetingApi

Patterns to add Hook to Existing Redux App

Method 1: Hook imported 👍

Method 2: Passed in Hook as Prop 👍

Example 3: Hook Passed to Prop as Reference

It will be Tooltip’s responsibility to call useTargetingApi at top level of a function component. It is possible Tooltip itself is still a class component and Tooltip pass the hook future down but the component that execute the hook must be functional component.

For testing, we can assign useTargetingAPI to be a function.

Method 3: Hook injected from HOC

One of the main learning is with hook, it is very easy to inject dependencies but it is very hard with Redux that requiring HOC to perform this.

Example 4: HOC that inject Hook

Method 4: Ad Hoc Hook

Figure 5: How to add mixed hook

The sample code for before adding hooks is in Example 4 below:

Example 4: React Component without Hook

The sample code for after adding hooks is in Example 5 below:

Example 5: React Component wrapped by Hook

Pay attention to the difference of what is passed to shallow in two tests. The first test shallow renders both function and React Component. The second test only shallow renders the Function Component.

How Hook Impact Re-render

  1. Prop changed
  2. State changed
  3. Context value changed

In a purely Redux app, the only context is Redux store which is a context. Only a Container has access to the Context and when Redux state changed, all containers will re-render. Putting it another way, any Redux state change will cause all containers to re-render. The component that is child of a container whose prop changed will also re-render as a result. When we use hook as component, the component will re-render if:

  1. Prop changed
  2. State from useState changed
  3. Context value from useContext changed

If you think through this, re-render should work automatically when we move from Redux to Hooks as long as the context value will change if a re-render is required due to something other than prop and state. In Figure 2, we purposefully put all the context state and inApi as value provided by context, therefore guaranteeing re-rendering when any of these changed since there should not be any re-render required if input and state of the context provider didn’t change. Relax knowing that this is already taken care of as long as context contains all the state and inApi from ContextProvider.

Build App around Context

Answer to Exercises

Hook is a Function Component only if it returns React.Element. Function component is a hook if it make hook calls such as useState.

Exercise 2

Should you write Class Component if you can solve with Function Component?
No. Function Component is easier to write and can be used as function. Function Component is more versatile and less restrictive.

Should you write hook if you can solve with function?
No. Function is easier to write and can be used as hook. Function is more versation and less restrictive.

Should you write component if you can solve with hook?
No. Hook is easier to write and can be used as component or function. Hook is more versatile (for example, using hook as function avoids creating unnecessary Components that has rendering overhead).

influencer and advocator

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store