or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

advanced-usage.mdcore-htm.mdindex.mdpreact-integration.mdreact-integration.md
tile.json

preact-integration.mddocs/

Preact Integration

HTM provides seamless integration with Preact through pre-configured imports that combine HTM's tagged template syntax with Preact's component system.

Import Paths

Standard Preact Integration

import { html, h, render, Component } from "htm/preact";

Standalone Preact Integration (with hooks)

import { 
  html, h, render, Component, createContext,
  useState, useEffect, useReducer, useRef, useMemo, useCallback,
  useContext, useLayoutEffect, useImperativeHandle, useDebugValue, useErrorBoundary
} from "htm/preact/standalone";

API Reference

html (Tagged Template Function)

declare const html: (strings: TemplateStringsArray, ...values: any[]) => VNode;

Pre-bound HTM function configured for Preact's

h
function.

Returns:

  • VNode
    - Preact virtual DOM node

Usage Examples:

import { html, render } from "htm/preact";

// Basic element
const element = html`<div class="container">Hello World</div>`;

// Dynamic content
const name = "Alice";
const greeting = html`<h1>Hello, ${name}!</h1>`;

// Event handlers
const handleClick = () => console.log('Clicked!');
const button = html`<button onclick=${handleClick}>Click me</button>`;

// Render to DOM
render(greeting, document.body);

Component Integration

import { html, Component, render } from "htm/preact";

// Functional component
const Welcome = ({ name }) => html`
  <div class="welcome">
    <h1>Welcome, ${name}!</h1>
  </div>
`;

// Class component
class Counter extends Component {
  constructor() {
    super();
    this.state = { count: 0 };
  }
  
  increment = () => {
    this.setState({ count: this.state.count + 1 });
  };
  
  render() {
    return html`
      <div class="counter">
        <p>Count: ${this.state.count}</p>
        <button onclick=${this.increment}>+</button>
      </div>
    `;
  }
}

// Usage
const app = html`
  <div>
    <${Welcome} name="Alice" />
    <${Counter} />
  </div>
`;

render(app, document.getElementById('root'));

Preact Re-exports

These functions are re-exported directly from Preact:

// Hyperscript function
declare function h(type: any, props: any, ...children: any[]): VNode;

// Render function
declare function render(tree: VNode, parent: HTMLElement): void;

// Component base class
declare class Component {
  constructor(props?: any, context?: any);
  setState(partial: any, callback?: () => void): void;
  forceUpdate(callback?: () => void): void;
  render(props?: any, state?: any, context?: any): VNode | null;
  componentDidMount?(): void;
  componentWillUnmount?(): void;
  componentDidUpdate?(previousProps: any, previousState: any, snapshot: any): void;
  shouldComponentUpdate?(nextProps: any, nextState: any, nextContext: any): boolean;
  getSnapshotBeforeUpdate?(previousProps: any, previousState: any): any;
  componentDidCatch?(error: any, errorInfo: any): void;
}

Hooks Integration

Standard Integration (htm/preact)

The standard integration re-exports all hooks from

preact/hooks
:

import { html, useState, useEffect, useRef } from "htm/preact";
// Note: hooks are available via re-export from preact/hooks

const HookComponent = () => {
  const [count, setCount] = useState(0);
  const buttonRef = useRef();
  
  useEffect(() => {
    console.log('Count changed:', count);
  }, [count]);
  
  return html`
    <div>
      <p>Count: ${count}</p>
      <button ref=${buttonRef} onclick=${() => setCount(count + 1)}>
        Increment
      </button>
    </div>
  `;
};

Standalone Integration (htm/preact/standalone)

The standalone version includes all hooks as direct exports:

// State hooks
declare function useState<T>(initialState: T | (() => T)): [T, (value: T | ((prev: T) => T)) => void];
declare function useReducer<T, A>(
  reducer: (state: T, action: A) => T,
  initialState: T
): [T, (action: A) => void];

// Effect hooks  
declare function useEffect(effect: () => void | (() => void), deps?: any[]): void;
declare function useLayoutEffect(effect: () => void | (() => void), deps?: any[]): void;

// Ref hooks
declare function useRef<T>(initialValue: T): { current: T };
declare function useImperativeHandle<T>(
  ref: any,
  createHandle: () => T,
  deps?: any[]
): void;

// Memoization hooks
declare function useMemo<T>(factory: () => T, deps: any[]): T;
declare function useCallback<T extends (...args: any[]) => any>(
  callback: T,
  deps: any[]
): T;

// Context hooks
declare function useContext<T>(context: Context<T>): T;

// Development hooks
declare function useDebugValue(value: any, formatter?: (value: any) => any): void;
declare function useErrorBoundary(): [any, () => void];

// Context creation
declare function createContext<T>(defaultValue: T): Context<T>;

interface Context<T> {
  Provider: any;
  Consumer: any;
}

Complete Usage Examples

Todo App with Hooks

import { html, render, useState, useEffect } from "htm/preact/standalone";

const TodoApp = () => {
  const [todos, setTodos] = useState([]);
  const [input, setInput] = useState('');
  
  const addTodo = () => {
    if (input.trim()) {
      setTodos([...todos, { id: Date.now(), text: input, done: false }]);
      setInput('');
    }
  };
  
  const toggleTodo = (id) => {
    setTodos(todos.map(todo => 
      todo.id === id ? { ...todo, done: !todo.done } : todo
    ));
  };
  
  const removeTodo = (id) => {
    setTodos(todos.filter(todo => todo.id !== id));
  };
  
  useEffect(() => {
    console.log(`${todos.length} todos`);
  }, [todos.length]);
  
  return html`
    <div class="todo-app">
      <h1>Todo App</h1>
      <div class="input-section">
        <input 
          type="text"
          value=${input}
          onInput=${(e) => setInput(e.target.value)}
          placeholder="Add a todo..."
        />
        <button onclick=${addTodo}>Add</button>
      </div>
      <ul class="todo-list">
        ${todos.map(todo => html`
          <li key=${todo.id} class=${todo.done ? 'done' : ''}>
            <span onclick=${() => toggleTodo(todo.id)}>
              ${todo.text}
            </span>
            <button onclick=${() => removeTodo(todo.id)}>×</button>
          </li>
        `)}
      </ul>
    </div>
  `;
};

render(html`<${TodoApp} />`, document.getElementById('root'));

Context and Custom Hooks

import { html, render, createContext, useContext, useState, useEffect } from "htm/preact/standalone";

// Create theme context
const ThemeContext = createContext({ theme: 'light', toggleTheme: () => {} });

// Custom hook
const useLocalStorage = (key, initialValue) => {
  const [value, setValue] = useState(() => {
    const saved = localStorage.getItem(key);
    return saved ? JSON.parse(saved) : initialValue;
  });
  
  useEffect(() => {
    localStorage.setItem(key, JSON.stringify(value));
  }, [key, value]);
  
  return [value, setValue];
};

// Theme provider component
const ThemeProvider = ({ children }) => {
  const [theme, setTheme] = useLocalStorage('theme', 'light');
  
  const toggleTheme = () => {
    setTheme(theme === 'light' ? 'dark' : 'light');
  };
  
  return html`
    <${ThemeContext.Provider} value=${{ theme, toggleTheme }}>
      <div class="app ${theme}">
        ${children}
      </div>
    </${ThemeContext.Provider}>
  `;
};

// Component using context
const ThemedButton = ({ children }) => {
  const { theme, toggleTheme } = useContext(ThemeContext);
  
  return html`
    <button 
      class="themed-button ${theme}"
      onclick=${toggleTheme}
    >
      ${children} (${theme} mode)
    </button>
  `;
};

// App
const App = () => html`
  <${ThemeProvider}>
    <h1>Themed App</h1>
    <${ThemedButton}>Toggle Theme<//>
  </${ThemeProvider}>
`;

render(html`<${App} />`, document.getElementById('root'));

Types

// Preact VNode type
interface VNode<P = any> {
  type: any;
  props: P & { children?: any };
  key?: any;
  ref?: any;
  startTime?: number;
  endTime?: number;
}

// HTML template function type
type HTMLTemplate = (strings: TemplateStringsArray, ...values: any[]) => VNode;

// Component types
type FunctionalComponent<P = {}> = (props: P & { children?: any }) => VNode | null;

interface ComponentClass<P = {}, S = {}> {
  new (props: P, context?: any): Component<P, S>;
}

// Hook types
type StateUpdater<T> = (value: T | ((prevState: T) => T)) => void;
type EffectCallback = () => void | (() => void);
type DependencyList = ReadonlyArray<any>;

// Context type
interface Context<T> {
  Provider: ComponentClass<{ value: T }>;
  Consumer: ComponentClass<{ children: (value: T) => VNode }>;
}