useHook hook
How to use the useHook hook to create your custom advanced hook.
Description
At some point you may want to further customize how wompo works, and maybe add a specific functionality to your components that Wompo doesn't actually support natively. The useHook hook will let you have access to the component's instance and the hook index.
This hook should only be used to create advanced custom hooks. The already present Wompo's hooks cover 90% of the average cases, and you can create a custom hook by simply combining them. See the
Usage
const [component, hookIndex] = useHook();The useHook hook accepts no parameters and will return always an array with two values: the instance of the currently rendering component, and the current hook index of the component.
But what is the hook index? Let's analyze how Wompo hooks work in the next section.
Deep dive into Wompo hooks
Maybe you already wondered how can Wompo return always the same values when you use hooks if they have no reference about the current component. The answer is that hooks are not pure functions. Every time a component renders, the value of an external variable called currentRenderingComponent is set to the instance of the current rendering component (big surprise huh?). This instance is the same returned by the useHook hook. Then, another external variable called currentHookIndex is set to 0, and incremented every time a hook is called inside of the component. The hook value is internally determined by the hook itself, and also what to return. The hook will be saved into the hooks array that every component has (you can actually select a wompo element in the console and write $0.hooks to see it).
It's something like this (very approximatively):
let currentRenderingComponent = null;
let currentHookIndex = 0;
function useAnyHook(value) {
// The hook is stored
currentRenderingComponent.hooks[currentHookIndex] = value;
// The hook index is incremented
currentHookIndex++;
return value;
}
// This is your custom component
function CustomComponent() {
const hook1 = useAnyHook(0);
const hook2 = useAnyHook({});
const hook3 = useAnyHook([]);
return html`...`;
}
// This is the class that will be generated for your component
class Wompo extends HTMLElement {
render() {
// Setting the currentRenderingComponent to "this" instance
currentRenderingComponent = this;
// Resetting the hook index
currentHookIndex = 0;
// Calling the component
CustomComponent(this.props);
}
}The code above will not work, but can make you easily get how Wompo works under the hood.
If you understand this, you also understand why it is so important for your component's hooks to be called in the first lines of the component and why they should NOT be inside conditional statements or loops.
Going back to the useHook explanation, the hook will simply return the current rendering component instance and the current hook index. It will also take care of automatically incrementing the hook index, so that you won't have to do it.
What it will not automatically do is set the hook value in the component's hooks array, but we will see how to do it in a moment with an example.
Because with this hook you have access to the component's instance, you can call methods on it or perform modifications. You can see the available methods and data accessible through a component's instance in the
Enough. Let's explore a nice example to see in practice how powerful this hook can be.
Example: useBattery
A very simple example can be creating a hook that uses the Web Battery API to get the battery level and status.
import { useHook } from 'wompo';
export default function useBattery() {
const [component, hookIndex] = useHook();
// Check if the hook already exists on the component
if (!component.hooks.hasOwnProperty(hookIndex)) {
// Hook not found, make initializations
const batteryHook = {
value: 'Getting the battery status...',
charging: false,
};
// Save the hook in the component's hooks
component.hooks[hookIndex] = batteryHook;
navigator.getBattery().then((battery) => {
// We have the battery informations, update the hook.
component.hooks[hookIndex].value = battery.level;
component.hooks[hookIndex].charging = battery.charging;
component.requestRender(); // Requests a new render
battery.addEventListener('chargingchange', () => {
// Charging value is updated
component.hooks[hookIndex].charging = battery.charging;
component.requestRender();
});
battery.addEventListener('levelchange', () => {
// Battery level changed, update the component that uses it
component.hooks[hookIndex].charging = battery.level;
component.requestRender();
});
});
}
return component.hooks[hookIndex];
}Did it! Now you can simply call the useBattery() hook inside of your components!
Info: you could have actually get the same result using native Wompo hooks like
To know how to make a custom hook combining the already existing ones, see the
Never modify the value of another hook. Always modify the component.hooks[hookIndex] hook. Modifying other hooks may break the component and create unexpected behaviors.
Subscribers
At some point you may want to create a hook that has a Set of subscribers components. What it means is that you may want this hook to register all the components that use that hook and perform actions on them when something happens. A great example can be implementing a global stateful storage. This kind of approach is currently used in the onDisconnected callback, like this:
const subscribers = new Set();
// ...
subscribers.forEach((component) => {
// Get the old onDisconnected callback
const oldDisconnectedCallback = component.onDisconnected;
// Override it with a new function
component.onDisconnected = () => {
subscribers.delete(component);
// But still execute the old callback!!
oldDisconnectedCallback();
};
});It is very important to still execute the old onDisconnected callback. If you don't, you may compromise the correct component's behavior and have performance impacts on your application.
If you don't handle properly what happens when a subscriber is unmounted you can pollute the memory with unused resources and perform re-renders of components that are not even in the DOM and so that are not even visible to the user. You always want to be careful when creating your own advanced hook, and handle your events appropriately.
Effects
Another thing you may want to do is create a custom "effect hook", or simply create a "cleanup function" (like the
Example:
function useInterval(callback, time) {
const [component, hookIndex] = useHook();
if (!component.hooks.hasOwnProperty(hookIndex)) {
const intervalId = setInterval(callback, time);
component.hooks[hookIndex] = {
value: 'anything you want',
cleanupFunction: () => {
clearInterval(intervalId);
},
};
}
return component.hooks[hookIndex].value;
}useExposed - Wompo hooks
The useExposed hook will let you expose some values and/or functions in the comonent's instance in the DOM.
useId - Wompo hooks
The useId hook allows to create a pseudo-random unique ID to use inside your components.