or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

app-components.mdblockdom.mdhooks.mdindex.mdlifecycle.mdreactivity.mdtemplates.mdutils-validation.md

hooks.mddocs/

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

```