React Hooks Are Great to Reuse Stateful Logic But What’s The Missing Piece?
What is React Hooks?
React Hooks have been officially released with React v16.8.0 on 6th Feb 2019. With React Hooks, you can easily introduce stateful logic into your React Function Components. Here is an example:
At line 6, we call the useState
hook to create a local state count
and add it to the function component Example
. To update the local state count
, just simply call the update function setCount
.
Here, you don’t need to worry about the useState(0)
call during the rendering will reset your local state to initial value 0 as the useState
will only initialize the local state on the first call.
With React Hooks, we just added the simple counter logic to our Example
component. However, the most powerful part comes in when you start to add more stateful logic and try to share it.
Why React Hooks?
As Sophie Alpert and Dan Abramov explained on React Conf 2008, React hooks were created to solve the issue that:
It’s hard to reuse stateful logic between components.
To achieve that, React Hooks allows you to extract your stateful logic into reusable functions namedCustom Hooks
and you can then use them as built-in React Hooks in your function components.
Addressing Cross-Cutting Concerns & Why?
Before React Hooks are available, component lifecycle methods are the only APIs we have to supply our component implementation to React. It works similar to callbacks that React will call those lifecycle methods at particular times along the component’s lifecycle to run the user-supplied logic.
However, component lifecycle methods are class methods and you can only override any class method once. This lures users into writing more and more different purposes stateful logic scattering into lifecycle methods.
componentDidMount() {
// Subscribe to API changes
Api.addChangeListener(this.handleApiChange);
...
EventSource.addChangeListener(this.handleEventChange);
}componentWillUnmount() {
// Clean up listeners
Api.removeChangeListener(this.handleApiChange);
...
EventSource.removeChangeListener(this.handleEventChange);
}
In order to handle these cross-cutting concerns, many patterns have been introduced into React’s ecosystem, such as mixins, render props and higher-order components, to extract that logic from your components and make them reusable.
Among those solutions, React Hooks definitely is the best solution so far as it doesn’t require restructuring your component nor introducing extra `wrappers`.
But is it the only problem we face when trying to share logic?
Stateful Component Composition
Besides sharing logic by cross-cutting concerns or aspects, we often want to share the logic by Component Composition as well. In fact, Component Composition is also the approach recommended by the React team to reuse code and arguably could be a more intuitive way.
However, the Component Composition approach in React community often talks about creating stateful parent component (or we call Container Components ) with stateless child components (or we call Presentational Components).
Can we create a new component (parent component) with stateful child components? If we can’t, do we need that?
The Missing Component Interface
There is no immediate technical obstacle that stops us to put together a parent component with stateful child components just like what we normally do with stateless child components.
But we soon will hit into the trouble of controlling stateful child components’ behavior from parent component as there is no easy way to alter stateful child components’ internal state.
Unlike stateful components, a stateless component’s behaviors/ render outcome is solely determined by its props
. Therefore, the parent component can fully control the stateless child components via their props.
On the other hand, a stateful component’s behaviors/ render outcome is not only determined by its props
but also controlled by its internal state as well. How could we alter child component’s state through the only component interface React’s component model offers — props
?
Many people try to solve this problem. One of the well-discussed solutions is “Derived State”.
Derived State
Derived State is the approach that enables a component to update its internal state as the result of changes in props. Previously, it can be implemented through the lifecycle method. In version 16.3, react introduced a replacement lifecycle, getDerivedStateFromProps
to solve the same use cases in a safer way.
However, Derived State is clumsy to use as props
is more for configuring the component and not capable of conveying complex messages. e.g. it’s probably feasible to ask a child component to reset
its internal state by changing a prop value (as the example below). But it’s hard to communicate with child components with the message like “set the state to this value”.
As suggested by this blog from the React team, Derived State is hard to implement and easy to introduce bugs and recommend that people should use Derived State Sparingly
. It’s said:
You Probably Don’t Need Derived State
I agree. We probably don’t need Derived State if we:
- Lift State Up. i.e. move stateful logic to the parent component and convent child components to Dumb Components. But what if the stateful design just suit the child component better? Besides we probably can’t keep lifting state up to create a giant root component. This may make code reuse even harder even with React Hooks.
- Or alter State using component instance methods & Component Ref. This approach requires child component author defines instance methods as an alternative interface to update internal state. In order to access the instance methods, the parent component needs to retrieve child component instance reference via Component Ref first. This creates tight coupled code and hurts component encapsulation as well.
If all those solutions don’t work well, can we create an alternative interface to communicate with child components and alter the internal state?
The Missing Interface: Inter-component Messaging
As one of the most popular state management library, Redux comes with an Action based event dispatch system. Can we build an Inter-component Messaging system with Redux’s action dispatch system?
To do so, we need to solve the problem of how could we implicitly determine the recipient component. As a communication mechanism aiming at helping component reuse & encapsulation, the action emitter component shouldn’t be required to specify target action recipient components and the target action recipient component shouldn’t be required to subscribe to any action dispatcher/source. Instead, the action should be dispatched to target action recipient component based on predefined action propagation mechanism.
Most frontend developers are familiar with the DOM event model as it’s used to be the key part of frontend development prior to React era. It comes with event bubbling mechanism that dispatches events to all elements on the propagation path. We probably can create a similar action propagation mechanism to achieve similar functionality.
fractal-component’s Event Model
fractal-component is a javascript library created for this purpose. It’s built on top of Redux & redux-saga and implement an action propagation mechanism based on its component namespace tree concept.
In this action dispatch model, every component is assigned a namespace (which is a “/” separated string) and a full namespace path (that is assigned based on the component’s namespace). e.g.:
- Namespace:
io.github.t83714/Counter
- Full Namespace Path:
ExampleApp/io.github.t83714/Counter/c0
All components in your app form a namespace tree as shown in the graph above. Compared with the DOM event model’s event bubbling propagation, fractal-component will dispatch an action from any node of the namespace tree to lower (child) nodes rather than higher-level ancestors (parent) nodes.
e.g. An action that is dispatched at ExampleApp/GifPair
will only be dispatched to Component A & Component B.
This design helps a simpler (and low-cost) Action routing implementation and make Component Composition easier as well.
More details of fractal-component’s event dispatching can be found from here.
Using fractal-component with React Hooks
fractal-component comes with a Custom React Hook (useComponentManager) that allows you to enhance your React function component with an advanced Inter-component Messaging (Action Dispatch/Routing) interface:
Once an action is dispatched to a component, the component can consume the action via two interfaces:
- reducer: As shown in line 19 above. It’s a hot-plug namespaced Redux Reducer that specifies how the component’s state changes in response to actions received. That’s the only way you can alter the component’s state.
- saga: As shown in line 27 above. Conceptually, a Saga is like a separate thread created for your component to run an event loop or managing side-effects. You can supply a Generator Function (or we call
Component Main Saga
) to run as a concurrent process. Although it still physically run in a single thread environment, thesaga
model allows us to block asaga
whenwait
for action without blocking other javascript code execution or fork a newsaga
to create side effects concurrently. We internally use redux-saga to run thesaga
. More details can be found here.
Summary
React Hooks is an excellent solution for sharing component stateful logic. However, when it comes to sharing stateful logic by Stateful Component Composition, we might need a new solution to provide a more effective way to communicate with stateful child components. fractal-component as a javascript library offers Inter-component action dispatch with action propagation could fill the gap in this area.