0
# Component Hooks
1
2
Hook functions for component state management, lifecycle integration, and accessing component context and environment.
3
4
## Capabilities
5
6
### useState
7
8
Creates reactive state that automatically triggers component re-renders when modified.
9
10
```typescript { .api }
11
/**
12
* Creates reactive state for a component
13
* @template T - State type
14
* @param state - Initial state value
15
* @returns Reactive state proxy
16
*/
17
function useState<T>(state: T): T;
18
```
19
20
**Usage Examples:**
21
22
```typescript
23
import { Component, xml, useState } from "@odoo/owl";
24
25
class Counter extends Component {
26
static template = xml`
27
<div>
28
<p>Count: <t t-esc="state.count"/></p>
29
<button t-on-click="increment">+</button>
30
</div>
31
`;
32
33
setup() {
34
// Simple primitive state
35
this.state = useState({ count: 0 });
36
}
37
38
increment() {
39
this.state.count++; // Automatically triggers re-render
40
}
41
}
42
43
class TodoApp extends Component {
44
static template = xml`
45
<div>
46
<input t-model="state.newTodo" />
47
<button t-on-click="addTodo">Add</button>
48
<ul>
49
<li t-foreach="state.todos" t-as="todo" t-key="todo.id">
50
<span t-esc="todo.text" />
51
<button t-on-click="() => this.removeTodo(todo.id)">Remove</button>
52
</li>
53
</ul>
54
</div>
55
`;
56
57
setup() {
58
// Complex object state
59
this.state = useState({
60
todos: [],
61
newTodo: "",
62
filter: "all"
63
});
64
}
65
66
addTodo() {
67
if (this.state.newTodo.trim()) {
68
this.state.todos.push({
69
id: Date.now(),
70
text: this.state.newTodo.trim(),
71
completed: false
72
});
73
this.state.newTodo = "";
74
}
75
}
76
77
removeTodo(id) {
78
const index = this.state.todos.findIndex(t => t.id === id);
79
if (index >= 0) {
80
this.state.todos.splice(index, 1);
81
}
82
}
83
}
84
```
85
86
### useComponent
87
88
Gets a reference to the current component instance.
89
90
```typescript { .api }
91
/**
92
* Returns the current component instance
93
* @returns Current component instance
94
*/
95
function useComponent(): Component;
96
```
97
98
**Usage Examples:**
99
100
```typescript
101
import { Component, xml, useComponent } from "@odoo/owl";
102
103
class MyComponent extends Component {
104
static template = xml`<div>Component with self-reference</div>`;
105
106
setup() {
107
const component = useComponent();
108
console.log("Component instance:", component);
109
110
// Useful for accessing component from nested functions
111
setTimeout(() => {
112
component.render(); // Force re-render
113
}, 1000);
114
}
115
}
116
```
117
118
### useRef
119
120
Creates a reference to access DOM elements after rendering.
121
122
```typescript { .api }
123
/**
124
* Creates a reference to access DOM elements
125
* @template T - HTMLElement type
126
* @param name - Reference name (must match t-ref attribute in template)
127
* @returns Reference object with el property
128
*/
129
function useRef<T extends HTMLElement = HTMLElement>(name: string): { el: T | null };
130
```
131
132
**Usage Examples:**
133
134
```typescript
135
import { Component, xml, useRef, onMounted } from "@odoo/owl";
136
137
class FocusInput extends Component {
138
static template = xml`
139
<div>
140
<input t-ref="input" placeholder="Will be focused on mount" />
141
<button t-on-click="focusInput">Focus Input</button>
142
<canvas t-ref="canvas" width="200" height="100"></canvas>
143
</div>
144
`;
145
146
setup() {
147
this.inputRef = useRef("input");
148
this.canvasRef = useRef("canvas");
149
150
onMounted(() => {
151
// Focus input after component mounts
152
this.inputRef.el?.focus();
153
154
// Draw on canvas
155
const canvas = this.canvasRef.el;
156
if (canvas) {
157
const ctx = canvas.getContext("2d");
158
ctx.fillStyle = "blue";
159
ctx.fillRect(10, 10, 50, 30);
160
}
161
});
162
}
163
164
focusInput() {
165
this.inputRef.el?.focus();
166
}
167
}
168
```
169
170
### useEnv
171
172
Accesses the component's environment object.
173
174
```typescript { .api }
175
/**
176
* Gets the current component's environment
177
* @template E - Environment type
178
* @returns Component environment
179
*/
180
function useEnv<E>(): E;
181
```
182
183
**Usage Examples:**
184
185
```typescript
186
import { Component, xml, useEnv } from "@odoo/owl";
187
188
class ApiComponent extends Component {
189
static template = xml`
190
<div>
191
<button t-on-click="fetchData">Fetch Data</button>
192
<div t-if="state.data">
193
<t t-esc="state.data" />
194
</div>
195
</div>
196
`;
197
198
setup() {
199
this.env = useEnv();
200
this.state = useState({ data: null });
201
}
202
203
async fetchData() {
204
try {
205
const response = await fetch(this.env.apiUrl + '/data');
206
this.state.data = await response.json();
207
} catch (error) {
208
console.error("Failed to fetch:", error);
209
}
210
}
211
}
212
213
// Mount with environment
214
mount(ApiComponent, document.body, {
215
env: {
216
apiUrl: "https://api.example.com",
217
user: { id: 1, name: "John" }
218
}
219
});
220
```
221
222
### useSubEnv
223
224
Creates a sub-environment that extends the current environment.
225
226
```typescript { .api }
227
/**
228
* Extends the current environment with additional properties
229
* @param envExtension - Properties to add to environment
230
*/
231
function useSubEnv(envExtension: Env): void;
232
```
233
234
**Usage Examples:**
235
236
```typescript
237
import { Component, xml, useSubEnv, useEnv } from "@odoo/owl";
238
239
class ThemeProvider extends Component {
240
static template = xml`
241
<div class="theme-provider">
242
<t t-slot="default" />
243
</div>
244
`;
245
246
setup() {
247
// Extend environment with theme-related utilities
248
useSubEnv({
249
theme: {
250
primary: "#007bff",
251
secondary: "#6c757d",
252
isDark: this.props.darkMode
253
},
254
formatDate: (date) => date.toLocaleDateString(),
255
t: (key) => this.env.translations[key] || key
256
});
257
}
258
}
259
260
class ThemedButton extends Component {
261
static template = xml`
262
<button t-att-style="buttonStyle" t-on-click="handleClick">
263
<t t-esc="env.t(props.labelKey)" />
264
</button>
265
`;
266
267
setup() {
268
this.env = useEnv();
269
}
270
271
get buttonStyle() {
272
return `background-color: ${this.env.theme.primary}; color: white;`;
273
}
274
275
handleClick() {
276
console.log("Button clicked in theme:", this.env.theme.isDark ? "dark" : "light");
277
}
278
}
279
```
280
281
### useChildSubEnv
282
283
Creates a sub-environment specifically for child components.
284
285
```typescript { .api }
286
/**
287
* Creates a sub-environment for child components
288
* @param envExtension - Properties to add to child environment
289
*/
290
function useChildSubEnv(envExtension: Env): void;
291
```
292
293
### useEffect
294
295
Manages side effects with optional dependency tracking.
296
297
```typescript { .api }
298
/**
299
* Manages side effects with dependency tracking
300
* @param effect - Effect callback that receives dependencies and optionally returns cleanup function
301
* @param computeDependencies - Function that computes dependencies array for change detection
302
*/
303
function useEffect<T extends unknown[]>(
304
effect: (...dependencies: T) => void | (() => void),
305
computeDependencies?: () => [...T]
306
): void;
307
```
308
309
**Usage Examples:**
310
311
```typescript
312
import { Component, xml, useEffect, useState } from "@odoo/owl";
313
314
class TimerComponent extends Component {
315
static template = xml`
316
<div>
317
<p>Timer: <t t-esc="state.seconds"/>s</p>
318
<button t-on-click="toggleTimer">
319
<t t-esc="state.isRunning ? 'Stop' : 'Start'" />
320
</button>
321
</div>
322
`;
323
324
setup() {
325
this.state = useState({
326
seconds: 0,
327
isRunning: false
328
});
329
330
// Effect with cleanup
331
useEffect(() => {
332
let interval;
333
if (this.state.isRunning) {
334
interval = setInterval(() => {
335
this.state.seconds++;
336
}, 1000);
337
}
338
339
// Cleanup function
340
return () => {
341
if (interval) {
342
clearInterval(interval);
343
}
344
};
345
}, [this.state.isRunning]); // Re-run when isRunning changes
346
347
// Effect that runs once on mount
348
useEffect(() => {
349
console.log("Timer component mounted");
350
351
return () => {
352
console.log("Timer component unmounting");
353
};
354
}, []); // Empty dependencies = run once
355
}
356
357
toggleTimer() {
358
this.state.isRunning = !this.state.isRunning;
359
}
360
}
361
```
362
363
### useExternalListener
364
365
Adds event listeners to external DOM elements with automatic cleanup.
366
367
```typescript { .api }
368
/**
369
* Adds event listener to external elements with automatic cleanup
370
* @param target - Event target (DOM element, window, document, etc.)
371
* @param eventName - Event name to listen for
372
* @param handler - Event handler function
373
*/
374
function useExternalListener(
375
target: EventTarget,
376
eventName: string,
377
handler: EventListener,
378
eventParams?: AddEventListenerOptions
379
): void;
380
```
381
382
**Usage Examples:**
383
384
```typescript
385
import { Component, xml, useExternalListener, useState } from "@odoo/owl";
386
387
class WindowSizeTracker extends Component {
388
static template = xml`
389
<div>
390
<p>Window size: <t t-esc="state.width"/>x<t t-esc="state.height"/></p>
391
<p>Mouse position: (<t t-esc="state.mouseX"/>, <t t-esc="state.mouseY"/>)</p>
392
</div>
393
`;
394
395
setup() {
396
this.state = useState({
397
width: window.innerWidth,
398
height: window.innerHeight,
399
mouseX: 0,
400
mouseY: 0
401
});
402
403
// Listen to window resize
404
useExternalListener(window, "resize", () => {
405
this.state.width = window.innerWidth;
406
this.state.height = window.innerHeight;
407
});
408
409
// Listen to mouse movement on document
410
useExternalListener(document, "mousemove", (event) => {
411
this.state.mouseX = event.clientX;
412
this.state.mouseY = event.clientY;
413
});
414
415
// Listen to keyboard events
416
useExternalListener(document, "keydown", (event) => {
417
if (event.key === "Escape") {
418
console.log("Escape pressed!");
419
}
420
});
421
}
422
}
423
424
class ClickOutside extends Component {
425
static template = xml`
426
<div t-ref="container" class="dropdown">
427
<button t-on-click="toggle">Toggle Dropdown</button>
428
<ul t-if="state.isOpen" class="dropdown-menu">
429
<li>Option 1</li>
430
<li>Option 2</li>
431
<li>Option 3</li>
432
</ul>
433
</div>
434
`;
435
436
setup() {
437
this.containerRef = useRef("container");
438
this.state = useState({ isOpen: false });
439
440
// Close dropdown when clicking outside
441
useExternalListener(document, "click", (event) => {
442
if (this.state.isOpen &&
443
this.containerRef.el &&
444
!this.containerRef.el.contains(event.target)) {
445
this.state.isOpen = false;
446
}
447
});
448
}
449
450
toggle() {
451
this.state.isOpen = !this.state.isOpen;
452
}
453
}
454
```