2019-09-24 14:06:53 +02:00
|
|
|
# 🦉 Hooks 🦉
|
|
|
|
|
|
|
|
|
|
## Content
|
|
|
|
|
|
|
|
|
|
- [Overview](#overview)
|
2019-09-29 16:47:06 +02:00
|
|
|
- [Example: Mouse Position](#example-mouse-position)
|
|
|
|
|
- [Example: Autofocus](#example-autofocus)
|
2019-09-24 14:06:53 +02:00
|
|
|
- [Reference](#reference)
|
2019-09-26 21:56:01 +02:00
|
|
|
- [One Rule](#one-rule)
|
|
|
|
|
- [`useState`](#usestate)
|
|
|
|
|
- [`onMounted`](#onmounted)
|
|
|
|
|
- [`onWillUnmount`](#onwillunmount)
|
2019-10-03 11:56:29 +02:00
|
|
|
- [`onWillPatch`](#onwillpatch)
|
|
|
|
|
- [`onPatched`](#onpatched)
|
2019-10-08 09:00:06 +02:00
|
|
|
- [`useContext`](#usecontext)
|
2019-09-26 21:56:01 +02:00
|
|
|
- [`useRef`](#useref)
|
2019-10-07 15:51:45 +02:00
|
|
|
- [`useSubEnv`](#usesubenv)
|
2019-09-24 14:06:53 +02:00
|
|
|
|
|
|
|
|
## Overview
|
|
|
|
|
|
|
|
|
|
Hooks were popularised by React as a way to solve the following issues:
|
2019-09-26 21:56:01 +02:00
|
|
|
|
2019-09-24 14:06:53 +02:00
|
|
|
- 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 works beautifully with Owl components: they solve the problems mentioned
|
|
|
|
|
above, and in particular, they are the perfect way to make your component
|
|
|
|
|
reactive.
|
|
|
|
|
|
2019-09-29 16:47:06 +02:00
|
|
|
## Example: mouse position
|
2019-09-24 14:06:53 +02:00
|
|
|
|
|
|
|
|
Here is the classical example of a non trivial hook to track the mouse position.
|
|
|
|
|
|
|
|
|
|
```js
|
2019-09-26 21:56:01 +02:00
|
|
|
const { useState, onMounted, onWillUnmount } = owl.hooks;
|
2019-09-24 14:06:53 +02:00
|
|
|
|
|
|
|
|
// We define here a custom behaviour: this hook tracks the state of the mouse
|
|
|
|
|
// position
|
|
|
|
|
function useMouse() {
|
2019-09-26 21:56:01 +02:00
|
|
|
const position = useState({ x: 0, y: 0 });
|
|
|
|
|
|
|
|
|
|
function update(e) {
|
|
|
|
|
position.x = e.clientX;
|
|
|
|
|
position.y = e.clientY;
|
|
|
|
|
}
|
|
|
|
|
onMounted(() => {
|
|
|
|
|
window.addEventListener("mousemove", update);
|
|
|
|
|
});
|
|
|
|
|
onWillUnmount(() => {
|
|
|
|
|
window.removeEventListener("mousemove", update);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
return position;
|
2019-09-24 14:06:53 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Main root component
|
|
|
|
|
class App extends owl.Component {
|
2019-09-26 21:56:01 +02:00
|
|
|
static template = xml`
|
2019-09-29 16:47:06 +02:00
|
|
|
<div t-name="App">
|
|
|
|
|
<div>Mouse: <t t-esc="mouse.x"/>, <t t-esc="mouse.y"/></div>
|
|
|
|
|
</div>`;
|
2019-09-24 14:06:53 +02:00
|
|
|
|
2019-09-26 21:56:01 +02:00
|
|
|
// this hooks is bound to the 'mouse' property.
|
|
|
|
|
mouse = useMouse();
|
2019-09-24 14:06:53 +02:00
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
2019-09-26 21:56:01 +02:00
|
|
|
Note that we use the prefix `use` for hooks, just like in React. This is just
|
2019-09-24 14:06:53 +02:00
|
|
|
a convention.
|
|
|
|
|
|
2019-09-29 16:47:06 +02:00
|
|
|
## Example: autofocus
|
|
|
|
|
|
|
|
|
|
Hooks can be combined to create the desired effect. For example, the following
|
|
|
|
|
hook combines the `useRef` hook with the `onPatched` and `onMounted` functions
|
|
|
|
|
to create an easy way to focus an input whenever it appears in the DOM:
|
|
|
|
|
|
|
|
|
|
```js
|
|
|
|
|
function useAutofocus(name) {
|
|
|
|
|
let ref = useRef(name);
|
|
|
|
|
let isInDom = false;
|
|
|
|
|
function updateFocus() {
|
|
|
|
|
if (!isInDom && ref.el) {
|
|
|
|
|
isInDom = true;
|
|
|
|
|
ref.el.focus();
|
|
|
|
|
} else if (isInDom && !ref.el) {
|
|
|
|
|
isInDom = false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
onPatched(updateFocus);
|
|
|
|
|
onMounted(updateFocus);
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
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:
|
|
|
|
|
|
|
|
|
|
```js
|
|
|
|
|
class SomeComponent extends Component {
|
|
|
|
|
static template = xml`
|
|
|
|
|
<div>
|
|
|
|
|
<input />
|
|
|
|
|
<input t-ref="myinput"/>
|
|
|
|
|
</div>`;
|
|
|
|
|
|
|
|
|
|
constructor(...args) {
|
|
|
|
|
super(...args);
|
|
|
|
|
useAutofocus("myinput");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
2019-09-24 14:06:53 +02:00
|
|
|
## Reference
|
|
|
|
|
|
|
|
|
|
### One rule
|
|
|
|
|
|
|
|
|
|
There is only one rule: every hook for a component have to be called in the
|
|
|
|
|
constructor (or in class fields):
|
|
|
|
|
|
|
|
|
|
```js
|
|
|
|
|
// ok
|
|
|
|
|
class SomeComponent extends Component {
|
2019-09-29 16:47:06 +02:00
|
|
|
state = useState({ value: 0 });
|
2019-09-24 14:06:53 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// also ok
|
|
|
|
|
class SomeComponent extends Component {
|
2019-09-26 21:56:01 +02:00
|
|
|
constructor(...args) {
|
|
|
|
|
super(...args);
|
2019-09-29 16:47:06 +02:00
|
|
|
this.state = useState({ value: 0 });
|
2019-09-26 21:56:01 +02:00
|
|
|
}
|
2019-09-24 14:06:53 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// not ok: this is executed after the constructor is called
|
|
|
|
|
class SomeComponent extends Component {
|
2019-09-26 21:56:01 +02:00
|
|
|
async willStart() {
|
2019-09-29 16:47:06 +02:00
|
|
|
this.state = useState({ value: 0 });
|
2019-09-26 21:56:01 +02:00
|
|
|
}
|
2019-09-24 14:06:53 +02:00
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### `useState`
|
|
|
|
|
|
|
|
|
|
The `useState` hook is certainly the most important hooks for Owl components:
|
|
|
|
|
this is what enables 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`).
|
|
|
|
|
|
|
|
|
|
```javascript
|
|
|
|
|
const { useState } = owl.hooks;
|
|
|
|
|
|
|
|
|
|
class Counter extends owl.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++;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### `onMounted`
|
|
|
|
|
|
2019-10-03 11:56:29 +02:00
|
|
|
`onMounted` is not an user hook, but is a building block designed to help make useful
|
|
|
|
|
abstractions. `onMounted` registers a callback, which will be called when the component
|
2019-09-24 14:06:53 +02:00
|
|
|
is mounted (see example on top of this page).
|
|
|
|
|
|
|
|
|
|
### `onWillUnmount`
|
|
|
|
|
|
2019-10-03 11:56:29 +02:00
|
|
|
`onWillUnmount` is not an user hook, but is a building block designed to help make useful
|
|
|
|
|
abstractions. `onWillUnmount` registers a callback, which will be called when the component
|
2019-09-24 14:06:53 +02:00
|
|
|
is unmounted (see example on top of this page).
|
2019-09-26 21:56:01 +02:00
|
|
|
|
2019-10-03 11:56:29 +02:00
|
|
|
### `onWillPatch`
|
|
|
|
|
|
|
|
|
|
`onWillPatch` is not an user hook, but is a building block designed to help make useful
|
|
|
|
|
abstractions. `onWillPatch` registers a callback, which will be called just
|
|
|
|
|
before the component patched.
|
|
|
|
|
|
|
|
|
|
### `onPatched`
|
|
|
|
|
|
|
|
|
|
`onPatched` is not an user hook, but is a building block designed to help make useful
|
|
|
|
|
abstractions. `onPatched` registers a callback, which will be called just
|
|
|
|
|
after the component patched.
|
|
|
|
|
|
2019-10-08 09:00:06 +02:00
|
|
|
### `useContext`
|
|
|
|
|
|
|
|
|
|
See [`useContext`](context.md#usecontext) for reference documentation.
|
|
|
|
|
|
2019-09-26 21:56:01 +02:00
|
|
|
### `useRef`
|
|
|
|
|
|
|
|
|
|
The `useRef` hook is useful when we need a way to interact with some inside part
|
|
|
|
|
of a component, rendered by Owl. It can work either on a DOM node, or on a component,
|
|
|
|
|
tagged by the `t-ref` directive:
|
|
|
|
|
|
|
|
|
|
```xml
|
|
|
|
|
<div>
|
|
|
|
|
<div t-ref="someDiv"/>
|
|
|
|
|
<SubComponent t-ref="someComponent"/>
|
|
|
|
|
</div>
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
In this example, the component will be able to access the `div` and the component
|
|
|
|
|
`SubComponent` using the `useRef` hook:
|
|
|
|
|
|
|
|
|
|
```js
|
|
|
|
|
class Parent extends Component {
|
|
|
|
|
subRef = useRef("someComponent");
|
|
|
|
|
divRef = useRef("someDiv");
|
|
|
|
|
|
|
|
|
|
someMethod() {
|
|
|
|
|
// here, if component is mounted, refs are active:
|
|
|
|
|
// - this.divRef.el is the div HTMLElement
|
|
|
|
|
// - this.subRef.comp is the instance of the sub component
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
As shown by the example above, html elements are accessed by using the `el`
|
|
|
|
|
key, and components references are accessed with `comp`.
|
|
|
|
|
|
|
|
|
|
Note: if used on a component, the reference will be set in the `refs`
|
|
|
|
|
variable between `willPatch` and `patched`.
|
|
|
|
|
|
|
|
|
|
The `t-ref` directive also accepts dynamic values with string interpolation
|
|
|
|
|
(like the [`t-attf-`](qweb.md#dynamic-attributes) and
|
|
|
|
|
`t-component` directives). For example,
|
|
|
|
|
|
|
|
|
|
```xml
|
|
|
|
|
<div t-ref="component_{{someCondition ? '1' : '2'}}"/>
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
Here, the references needs to be set like this:
|
|
|
|
|
|
|
|
|
|
```js
|
|
|
|
|
this.ref1 = useRef("component_1");
|
|
|
|
|
this.ref2 = useRef("component_2");
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
References are only guaranteed to be active while the parent component is mounted.
|
|
|
|
|
If this is not the case, accessing `el` or `comp` on it will return `null`.
|
2019-10-04 22:00:05 +02:00
|
|
|
|
|
|
|
|
### `useSubEnv`
|
|
|
|
|
|
|
|
|
|
The environment is sometimes useful to share some common information between
|
2019-10-08 09:00:06 +02:00
|
|
|
all components. But sometimes, we want to _scope_ that knowledge to a subtree.
|
2019-10-04 22:00:05 +02:00
|
|
|
|
|
|
|
|
For example, if we have a form view component, maybe we would like to make some
|
|
|
|
|
`model` object available to all sub component, but not to the whole application.
|
|
|
|
|
This is where the `useSubEnv` hook may be useful: it let a component add some
|
|
|
|
|
information to the environment in a way that only the component and its children
|
|
|
|
|
can access it:
|
|
|
|
|
|
|
|
|
|
```js
|
|
|
|
|
class FormComponent extends Component {
|
|
|
|
|
constructor(...args) {
|
|
|
|
|
super(...args);
|
|
|
|
|
const model = makeModel();
|
|
|
|
|
useSubEnv({ model });
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
The `useSubEnv` takes one argument: an object which contains some key/value that
|
|
|
|
|
will be added to the parent environment. Note that it will extend, not replace
|
2019-10-08 09:00:06 +02:00
|
|
|
the parent environment. And of course, the parent environment will not be
|
|
|
|
|
affected.
|