globalize your React Hooks without fear using the Hookleton Pattern
Hookleton convert any React Hook in a global hook. A global hook is a function that always returns the same result to each place where it is called. Let's call this result, the hook runtime interface.
When this Hook is used for the first time its host
component will become a Singleton of it, hence the name Hookleton. Naming is hard!, you know.
That said, it might sound a bit complicated but it is not. Hookleton
was created thinking about the ease of use even for an occasional user with the minimum effort. It is likely that when you try it you will not want to use something else because there simply is nothing easier out there.
Does Hookleton make your life a little more easy? Consider
The Hookleton Pattern is a software design pattern that restricts the calls to a provided React Hook to a single component and uses a pub/sub mechanism to manage communication with the rest of user components of the hook
- Zero dependencies (only React Hook)
- Small size, ~50 LOC
- Simple API
- Low Memory Consumption and CPU Usage
- Very fast, as fast as the React Hook runtime
- 👉 without using React Context
- 👉 not complex user memoizations needed. Out of the box performance
- Works in any environment that supports React Hook: React Native, React Server-Side Rendering (next.js), Proto Native, ...
- Extensible
- Very low cognitive load
# NPM
npm i hookleton
# Yarn
yarn add hookleton
The Hookleton package exposes createHook
function that does all.
createHook(useHook, ...initial?): useHookleton
useHook
is the user provide Hookinitial
any number of params that useHook will accept
useHookleton
returned Hookleton. Called by non-host componentsuseHookleton.use
returned Hookleton. Called by the host componentuseHookleton.get
function that get the current output of the Hookleton. For standalone use
Only one component, the host
, can call created hookleton use
hook and this component must be at the top of the component hierarchy.
A simple example is worth a thousand words
import { useState } from 'react';
import { createHook } from 'hookleton';
// useCounter is a useState but global
const useCounter = createHook(useState);
const Increment = () => {
const [, update] = useCounter();
const increment = () => update(s => s + 1);
return <button onClick={increment}>+</button>;
};
const Decrement = () => {
const [, update] = useCounter();
const decrement = () => update(s => s - 1);
return <button onClick={decrement}>-</button>;
};
// The host component
const Value = () => {
const [count] = useCounter.use(0);
return <span>{count}</span>;
};
// Value componet must be at the top
export default () => (
<div>
<Value />
<Increment />
<Decrement />
</div>
);
The Value
component is the host of useCounter
hookleton for being the first component of the hierarchy that call useCounter.use
.
Remember that useCounter
is composing a useState
which is where all the logic happens.
The Hookleton library includes only the minimal core code needed to maintain state synchronization between the users of the hookleton but was designed to be fully extensible. Take a look at these projects, it could be useful:
- Garfio for extending Hookleton Pattern namespaces and more
Examples page include:
- Todo App page | source
- a Mouse event listener that notify
{x,y}
position to 1200 components in Real Time page | source - basic Counter showed above page | source
- showing Fetched 10 random users data in 16 components page | source
How would it be with React Context vs Hookletons?
- Counter based on React Context page | source
- Counter with Hookletons page | source
- Mouse based on React Context page | source
- Mouse with Hookletons page | source
The idea is very simple. The first time that a user component of the Hook is instantiated the hookleton is created and the result of the call to user Hook
will be linked to the host
of the hookleton. The user Hook
is the one you want to globalize.
The result to the calls to the hookleton in the host
component will become The source of truth
. The rest of user components will receive a reference to The source of truth on each re-render.
As we said the host
of the hookleton is the first component instance that call .use
hook. This is important because:
- Initial state of the Hook only can be defined in the
host
or in the creation moment withcreateHook
. Any initial value from the rest of components will be ignored.
This is all that you need about Hookletons before start to use it
The first reason is simplicity, but obviously this explanation is not enough. Let's do some history.
A cloudy day googling I was looking for the simplest possible alternative to Redux. For a toy project I needed to share a couple of values between components in the simplest way possible. I found several packages but I did not like any, mainly for two reasons.
- They force you to use Providers, Context objects or HOC wrappers
- They usually implement complex logics to update the state and avoid unnecessary rerenders
This does not mean that they are badly constructed, just that they were not built on top of React Hook, they are prior to it or do not benefit at all of the "Hook engine"
Hookleton solves it elegantly. Actually Hookleton does not know anything about the useHook
that want to be global. This means that its utility is to share the interface of the hook not only its state.
Hookletons can be used in all kinds of projects, both large and small. Remember that Hookleton does not impose anything, it's just a wrapper to useYourImagination
- Wherever you need share
logic
anddata
between related and non-related components - With sites, landing pages and MVP projects that do not require
immutable
data and complex boilerplates - When complex logics and
immutable
data are a requirement, Hooks can be composed using Hookletons
- There are two ways to set the initial value of the hookleton. When created with
createHook
and when it is called from the host component.- The value passed in
createHook
creation has priority - If it is
undefined
in createHook, the value set in thehost
component will be used. There is only one host component.
- The value passed in
initial
values passed to non-host components will be ignored- If the host component is unmounted, the calls to state
setter
will fail. To ensure that this does not happen, you can create a component that acts as the host of the hookleton or several of them at the top of the component hierarchy, Ex:
const Hookletons = () => {
useExample1.use(0);
useExample2.use('one');
useExample3.use({ three: 3 });
return null
}
const App = () => (
<Hookletons />
<rest of the components ...>
)
- When standalone API,
.get()
, the return value will be an empty array if it is called before render the component that host the hookleton
The speed of Hookleton depends completely on the implementation of React Hook. If you compare it with any implementation based on Hooks, it should have a similar performance.
If you compare it with other non-Hook based solutions, I do not know that it's the fastest thing out there. But the fair comparison would be against the React Hooks himself.
You can see how the rerender behave in the above provide examples: Mouse, Counters, and Fetch data
- FĂ©lix A.A. <> @bySabi
- Documentation improvement
- Feel free to send any PR