Odoo Web Library (OWL) is a modern, lightweight TypeScript UI framework for building reactive web applications with components, templates, and state management.
Hook functions for component state management, lifecycle integration, and accessing component context and environment.
Creates reactive state that automatically triggers component re-renders when modified.
/**
* Creates reactive state for a component
* @template T - State type
* @param state - Initial state value
* @returns Reactive state proxy
*/
function useState<T>(state: T): T;Usage Examples:
import { Component, xml, useState } from "@odoo/owl";
class Counter extends Component {
static template = xml`
<div>
<p>Count: <t t-esc="state.count"/></p>
<button t-on-click="increment">+</button>
</div>
`;
setup() {
// Simple primitive state
this.state = useState({ count: 0 });
}
increment() {
this.state.count++; // Automatically triggers re-render
}
}
class TodoApp extends Component {
static template = xml`
<div>
<input t-model="state.newTodo" />
<button t-on-click="addTodo">Add</button>
<ul>
<li t-foreach="state.todos" t-as="todo" t-key="todo.id">
<span t-esc="todo.text" />
<button t-on-click="() => this.removeTodo(todo.id)">Remove</button>
</li>
</ul>
</div>
`;
setup() {
// Complex object state
this.state = useState({
todos: [],
newTodo: "",
filter: "all"
});
}
addTodo() {
if (this.state.newTodo.trim()) {
this.state.todos.push({
id: Date.now(),
text: this.state.newTodo.trim(),
completed: false
});
this.state.newTodo = "";
}
}
removeTodo(id) {
const index = this.state.todos.findIndex(t => t.id === id);
if (index >= 0) {
this.state.todos.splice(index, 1);
}
}
}Gets a reference to the current component instance.
/**
* Returns the current component instance
* @returns Current component instance
*/
function useComponent(): Component;Usage Examples:
import { Component, xml, useComponent } from "@odoo/owl";
class MyComponent extends Component {
static template = xml`<div>Component with self-reference</div>`;
setup() {
const component = useComponent();
console.log("Component instance:", component);
// Useful for accessing component from nested functions
setTimeout(() => {
component.render(); // Force re-render
}, 1000);
}
}Creates a reference to access DOM elements after rendering.
/**
* Creates a reference to access DOM elements
* @template T - HTMLElement type
* @param name - Reference name (must match t-ref attribute in template)
* @returns Reference object with el property
*/
function useRef<T extends HTMLElement = HTMLElement>(name: string): { el: T | null };Usage Examples:
import { Component, xml, useRef, onMounted } from "@odoo/owl";
class FocusInput extends Component {
static template = xml`
<div>
<input t-ref="input" placeholder="Will be focused on mount" />
<button t-on-click="focusInput">Focus Input</button>
<canvas t-ref="canvas" width="200" height="100"></canvas>
</div>
`;
setup() {
this.inputRef = useRef("input");
this.canvasRef = useRef("canvas");
onMounted(() => {
// Focus input after component mounts
this.inputRef.el?.focus();
// Draw on canvas
const canvas = this.canvasRef.el;
if (canvas) {
const ctx = canvas.getContext("2d");
ctx.fillStyle = "blue";
ctx.fillRect(10, 10, 50, 30);
}
});
}
focusInput() {
this.inputRef.el?.focus();
}
}Accesses the component's environment object.
/**
* Gets the current component's environment
* @template E - Environment type
* @returns Component environment
*/
function useEnv<E>(): E;Usage Examples:
import { Component, xml, useEnv } from "@odoo/owl";
class ApiComponent extends Component {
static template = xml`
<div>
<button t-on-click="fetchData">Fetch Data</button>
<div t-if="state.data">
<t t-esc="state.data" />
</div>
</div>
`;
setup() {
this.env = useEnv();
this.state = useState({ data: null });
}
async fetchData() {
try {
const response = await fetch(this.env.apiUrl + '/data');
this.state.data = await response.json();
} catch (error) {
console.error("Failed to fetch:", error);
}
}
}
// Mount with environment
mount(ApiComponent, document.body, {
env: {
apiUrl: "https://api.example.com",
user: { id: 1, name: "John" }
}
});Creates a sub-environment that extends the current environment.
/**
* Extends the current environment with additional properties
* @param envExtension - Properties to add to environment
*/
function useSubEnv(envExtension: Env): void;Usage Examples:
import { Component, xml, useSubEnv, useEnv } from "@odoo/owl";
class ThemeProvider extends Component {
static template = xml`
<div class="theme-provider">
<t t-slot="default" />
</div>
`;
setup() {
// Extend environment with theme-related utilities
useSubEnv({
theme: {
primary: "#007bff",
secondary: "#6c757d",
isDark: this.props.darkMode
},
formatDate: (date) => date.toLocaleDateString(),
t: (key) => this.env.translations[key] || key
});
}
}
class ThemedButton extends Component {
static template = xml`
<button t-att-style="buttonStyle" t-on-click="handleClick">
<t t-esc="env.t(props.labelKey)" />
</button>
`;
setup() {
this.env = useEnv();
}
get buttonStyle() {
return `background-color: ${this.env.theme.primary}; color: white;`;
}
handleClick() {
console.log("Button clicked in theme:", this.env.theme.isDark ? "dark" : "light");
}
}Creates a sub-environment specifically for child components.
/**
* Creates a sub-environment for child components
* @param envExtension - Properties to add to child environment
*/
function useChildSubEnv(envExtension: Env): void;Manages side effects with optional dependency tracking.
/**
* Manages side effects with dependency tracking
* @param effect - Effect callback that receives dependencies and optionally returns cleanup function
* @param computeDependencies - Function that computes dependencies array for change detection
*/
function useEffect<T extends unknown[]>(
effect: (...dependencies: T) => void | (() => void),
computeDependencies?: () => [...T]
): void;Usage Examples:
import { Component, xml, useEffect, useState } from "@odoo/owl";
class TimerComponent extends Component {
static template = xml`
<div>
<p>Timer: <t t-esc="state.seconds"/>s</p>
<button t-on-click="toggleTimer">
<t t-esc="state.isRunning ? 'Stop' : 'Start'" />
</button>
</div>
`;
setup() {
this.state = useState({
seconds: 0,
isRunning: false
});
// Effect with cleanup
useEffect(() => {
let interval;
if (this.state.isRunning) {
interval = setInterval(() => {
this.state.seconds++;
}, 1000);
}
// Cleanup function
return () => {
if (interval) {
clearInterval(interval);
}
};
}, [this.state.isRunning]); // Re-run when isRunning changes
// Effect that runs once on mount
useEffect(() => {
console.log("Timer component mounted");
return () => {
console.log("Timer component unmounting");
};
}, []); // Empty dependencies = run once
}
toggleTimer() {
this.state.isRunning = !this.state.isRunning;
}
}Adds event listeners to external DOM elements with automatic cleanup.
/**
* Adds event listener to external elements with automatic cleanup
* @param target - Event target (DOM element, window, document, etc.)
* @param eventName - Event name to listen for
* @param handler - Event handler function
*/
function useExternalListener(
target: EventTarget,
eventName: string,
handler: EventListener,
eventParams?: AddEventListenerOptions
): void;Usage Examples:
import { Component, xml, useExternalListener, useState } from "@odoo/owl";
class WindowSizeTracker extends Component {
static template = xml`
<div>
<p>Window size: <t t-esc="state.width"/>x<t t-esc="state.height"/></p>
<p>Mouse position: (<t t-esc="state.mouseX"/>, <t t-esc="state.mouseY"/>)</p>
</div>
`;
setup() {
this.state = useState({
width: window.innerWidth,
height: window.innerHeight,
mouseX: 0,
mouseY: 0
});
// Listen to window resize
useExternalListener(window, "resize", () => {
this.state.width = window.innerWidth;
this.state.height = window.innerHeight;
});
// Listen to mouse movement on document
useExternalListener(document, "mousemove", (event) => {
this.state.mouseX = event.clientX;
this.state.mouseY = event.clientY;
});
// Listen to keyboard events
useExternalListener(document, "keydown", (event) => {
if (event.key === "Escape") {
console.log("Escape pressed!");
}
});
}
}
class ClickOutside extends Component {
static template = xml`
<div t-ref="container" class="dropdown">
<button t-on-click="toggle">Toggle Dropdown</button>
<ul t-if="state.isOpen" class="dropdown-menu">
<li>Option 1</li>
<li>Option 2</li>
<li>Option 3</li>
</ul>
</div>
`;
setup() {
this.containerRef = useRef("container");
this.state = useState({ isOpen: false });
// Close dropdown when clicking outside
useExternalListener(document, "click", (event) => {
if (this.state.isOpen &&
this.containerRef.el &&
!this.containerRef.el.contains(event.target)) {
this.state.isOpen = false;
}
});
}
toggle() {
this.state.isOpen = !this.state.isOpen;
}
}Install with Tessl CLI
npx tessl i tessl/npm-odoo--owl