Hooks
Hooks were popularised by React as a way to solve the following issues:
- help reusing stateful logic between components
- help organizing code by feature in complex components
- use state in functional components, without writing a class.
Owl hooks serve the same purpose, except that they work for class components (note: React hooks do not work on class components, and maybe because of that, there seems to be the misconception that hooks are in opposition to class. This is clearly not true, as shown by Owl hooks).
Hooks work beautifully with Owl components: they solve the problems mentioned above, and in particular, they are the perfect way to make your component reactive.
The Hook Rule
There is only one rule: every hook for a component has to be called in the setup method, or in class fields:
// okclass SomeComponent extends Component { state = useState({ value: 0 });}
// also okclass SomeComponent extends Component { setup() { this.state = useState({ value: 0 }); }}
// not ok: this is executed after the constructor is calledclass SomeComponent extends Component { async willStart() { this.state = useState({ value: 0 }); }}Lifecycle Hooks
All lifecycle hooks are documented in detail in their specific section.
| Hook | Description |
|---|---|
| onWillStart | async, before first rendering |
| onWillRender | just before component is rendered |
| onRendered | just after component is rendered |
| onMounted | just after component is rendered and added to the DOM |
| onWillUpdateProps | async, before props update |
| onWillPatch | just before the DOM is patched |
| onPatched | just after the DOM is patched |
| onWillUnmount | just before removing component from DOM |
| onWillDestroy | just before component is destroyed |
| onError | catch and handle errors (see error handling page) |
Other Hooks
useState
The useState hook is certainly the most important hook for Owl components:
this is what allows a component to be reactive, to react to state change.
The useState hook has to be given an object or an array, and will return
an observed version of it (using a Proxy).
const { useState, Component } = owl;
class Counter extends Component { static template = xml` <button t-on-click="increment"> Click Me! [<t t-esc="state.value"/>] </button>`;
state = useState({ value: 0 });
increment() { this.state.value++; }}It is important to remember that useState only works with objects or arrays. It
is necessary, since Owl needs to react to a change in state.
useRef
The useRef hook is useful when we need a way to interact with some inside part
of a component, rendered by Owl. It only work on a html element tagged by the
t-ref directive:
<div> <input t-ref="someInput"/> <span>hello</span></div>In this example, the component will be able to access the input with the useRef hook:
class Parent extends Component { inputRef = useRef("someInput");
someMethod() { // here, if component is mounted, refs are active: // - this.inputRef.el is the input HTMLElement }}As shown by the example above, the actual HTMLElement instance is accessed with
the el key.
The t-ref directive also accepts dynamic values with string interpolation
(like the t-attf- and
t-component directives). For example,
<div t-ref="div_{{someCondition ? '1' : '2'}}"/>Here, the references need to be set like this:
this.ref1 = useRef("div_1");this.ref2 = useRef("div_2");References are only guaranteed to be active while the parent component is mounted.
If this is not the case, accessing el on it will return null.
useSubEnv and useChildSubEnv
The environment is sometimes useful to share some common information between all components. But sometimes, we want to scope that knowledge to a subtree.
For example, if we have a form view component, maybe we would like to make some
model object available to all sub components, but not to the whole application.
This is where the useChildSubEnv hook may be useful: it lets a component add some
information to the environment in a way that only its children
can access it:
class FormComponent extends Component { setup() { const model = makeModel(); // model will be available on this.env for this component and all children useSubEnv({ model }); // someKey will be available on this.env for all children useChildSubEnv({ someKey: "value" }); }}The useSubEnv and useChildSubEnv hooks take one argument: an object which
contains some key/value that will be added to the current environment. These hooks
will create a new env object with the new information:
useSubEnvwill assign this newenvto itself and to all children componentsuseChildSubEnvwill only assign this newenvto all children components.
As usual in Owl, environments created with these two hooks are frozen, to prevent unwanted modifications.
Note that both these hooks can be called an arbitrary number of times. The env
will then be updated accordingly.
useExternalListener
The useExternalListener hook helps solve a very common problem: adding and removing
a listener on some target whenever a component is mounted/unmounted. It takes a target
as its first argument, forwards the other arguments to addEventListener. For example,
a dropdown menu (or its parent) may need to listen to a click event on window
to be closed:
useExternalListener(window, "click", this.closeMenu, { capture: true });useComponent
The useComponent hook is useful as a building block for some customized hooks,
that may need a reference to the component calling them.
function useSomething() { const component = useComponent(); // now, component is bound to the instance of the current component}useEnv
The useEnv hook is useful as a building block for some customized hooks,
that may need a reference to the env of the component calling them.
function useSomething() { const env = useEnv(); // now, env is bound to the env of the current component}useEffect
This hook will run a callback when a component is mounted and patched, and will run a cleanup function before patching and before unmounting the the component (only if some dependencies have changed).
It has almost the same API as the React useEffect hook, except that the dependencies
are defined by a function instead of just the dependencies.
The useEffect hook takes two function: the effect function and the dependency
function. The effect function perform some task and return (optionally) a cleanup
function. The dependency function returns a list of dependencies, these dependencies
are passed as parameters in the effect function . If any of these
dependencies changes, then the current effect will be cleaned up and reexecuted.
Here is an example without any dependencies:
useEffect( () => { window.addEventListener("mousemove", someHandler); return () => window.removeEventListener("mousemove", someHandler); }, () => []);In the example above, the dependency list is empty, so the effect is only cleaned up when the component is unmounted.
If the dependency function is skipped, then the effect will be cleaned up and rerun at every patch.
Here is another example, of how one could implement a useAutofocus hook with
the useEffect hook:
function useAutofocus(name) { let ref = useRef(name); useEffect( (el) => el && el.focus(), () => [ref.el] );}This hook takes the name of a valid t-ref directive, which should be present
in the template. It then checks whenever the component is mounted or patched if
the reference is not valid, and in this case, it will focus the node element.
This hook can be used like this:
class SomeComponent extends Component { static template = xml` <div> <input /> <input t-ref="myinput"/> </div>`;
setup() { useAutofocus("myinput"); }}Example: mouse position
Here is the classical example of a non trivial hook to track the mouse position.
const { useState, onWillDestroy, Component } = owl;
// We define here a custom behaviour: this hook tracks the state of the mouse// positionfunction useMouse() { const position = useState({ x: 0, y: 0 });
function update(e) { position.x = e.clientX; position.y = e.clientY; } window.addEventListener("mousemove", update); onWillDestroy(() => { window.removeEventListener("mousemove", update); });
return position;}
// Main root componentclass Root extends Component { static template = xml`<div>Mouse: <t t-esc="mouse.x"/>, <t t-esc="mouse.y"/></div>`;
// this hooks is bound to the 'mouse' property. mouse = useMouse();}Note that we use the prefix use for hooks, just like in React. This is just
a convention.