Hooks ​
Overview ​
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
Owl hooks serve the same purpose, and they work for class components. They are the perfect way to make your component reactive and to encapsulate reusable logic.
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:
// ok
class SomeComponent extends Component {
state = proxy({ value: 0 });
}
// also ok
class SomeComponent extends Component {
setup() {
this.state = proxy({ value: 0 });
}
}
// not ok: this is executed after the constructor is called
class SomeComponent extends Component {
async willStart() {
this.state = proxy({ value: 0 });
}
}Lifecycle Hooks ​
All lifecycle hooks are documented in detail in their specific section.
| Hook | Description |
|---|---|
| onWillStart | async, before first rendering |
| onMounted | just after component is rendered and added to the DOM |
| 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) |
onWillStart and onWillDestroy also work inside a plugin's setup(). onWillStart defers the owning App.mount() (or the providePlugins owner component's first render) until all plugin async initialization resolves. See Plugins — Async Initialization.
Other Hooks ​
useEffect ​
The useEffect hook creates an effect that is automatically cleaned up when the component is destroyed. It is equivalent to:
onWillDestroy(effect(fn));The effect function runs immediately and re-runs whenever any reactive value (signal, computed, proxy property) read during execution changes. If the effect function returns a function, that function is called before each re-run, allowing resource cleanup.
class MyComponent extends Component {
static template = xml`<div/>`;
count = signal(0);
setup() {
useEffect(() => {
console.log("count is", this.count());
});
}
}Here is an example of a useAutofocus hook built with useEffect:
function useAutofocus() {
const ref = signal(null);
useEffect(() => {
const el = ref();
if (el) {
el.focus();
}
});
return ref;
}class SomeComponent extends Component {
static template = xml`
<div>
<input />
<input t-ref="this.inputRef"/>
</div>`;
inputRef = useAutofocus();
}useListener ​
The useListener hook adds an event listener to a target and automatically removes it when the component is destroyed. It accepts either a direct EventTarget (e.g. window, document) or a signal containing an element reference:
// Listen on window — added immediately, removed on destroy
useListener(window, "click", this.closeMenu, { capture: true });
// Listen on a ref signal — effect-based, re-attaches when element changes
const ref = signal(null);
useListener(ref, "scroll", this.onScroll);useApp ​
The useApp hook returns the current App instance:
setup() {
const app = useApp();
}useScope ​
The useScope hook returns the current Scope — the lifetime handle for the current component or plugin. It is the primary entry point for async cancellation (via scope.abortSignal or scope.until) and for capturing the current scope to run code in it later.
setup() {
const scope = useScope();
onWillStart(async () => {
const data = await scope.until(fetchData());
this.data = data;
});
}See the Scope page for full details.
Example: mouse position ​
Here is the classical example of a non trivial hook to track the mouse position.
function useMouse() {
const position = proxy({ 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;
}
class Root extends Component {
static template = xml`<div>Mouse: <t t-out="this.mouse.x"/>, <t t-out="this.mouse.y"/></div>`;
mouse = useMouse();
}Note that we use the prefix use for hooks, just like in React. This is just a convention.