> ## Documentation Index
> Fetch the complete documentation index at: https://mintlify.com/preactjs/preact/llms.txt
> Use this file to discover all available pages before exploring further.

# Options API

> Extend and customize Preact's behavior using the options hooks system

# Options API

The Options API is Preact's extension mechanism that allows you to hook into the rendering lifecycle and customize its behavior. This is the foundation for addons like `preact/debug`, `preact/compat`, and `preact/hooks`.

<Note>
  The Options API is primarily for library authors and advanced use cases. Most application developers won't need to use it directly.
</Note>

## Overview

The options object contains callback functions that Preact invokes at various stages of the rendering process:

```javascript theme={null}
import { options } from 'preact';

// Hook into the rendering lifecycle
options.vnode = (vnode) => {
  console.log('VNode created:', vnode);
};
```

Source: `src/options.js:1-17`

## Public Options Hooks

These hooks are part of Preact's public API and safe to use:

### vnode

Invoked whenever a VNode is created, before it's processed:

```javascript theme={null}
options.vnode = (vnode) => {
  // Modify or inspect vnodes as they're created
  console.log('Creating:', vnode.type);
};
```

**Type**: `(vnode: VNode) => void`

**Use cases**:

* Inject additional props
* Track component creation
* Implement custom prop transformations
* Add metadata to vnodes

Source: `src/index.d.ts:329`

**Example - Auto-inject props**:

```javascript theme={null}
options.vnode = (vnode) => {
  if (typeof vnode.type === 'function') {
    // Add a debug ID to every component
    vnode.props = {
      ...vnode.props,
      __debugId: Math.random().toString(36)
    };
  }
};
```

### diffed

Invoked after a vnode has been diffed and rendered:

```javascript theme={null}
options.diffed = (vnode) => {
  // Called after component renders
  console.log('Rendered:', vnode.type);
};
```

**Type**: `(vnode: VNode) => void`

**Use cases**:

* Track render completion
* Measure render performance
* Update external state after render
* Implement custom side effects

Source: `src/index.d.ts:333`

**Example - Performance monitoring**:

```javascript theme={null}
const renderTimes = new Map();

options.vnode = (vnode) => {
  if (typeof vnode.type === 'function') {
    vnode.startTime = performance.now();
  }
};

options.diffed = (vnode) => {
  if (vnode.startTime) {
    const duration = performance.now() - vnode.startTime;
    const name = vnode.type.displayName || vnode.type.name;
    console.log(`${name} rendered in ${duration.toFixed(2)}ms`);
  }
};
```

### unmount

Invoked immediately before a vnode is unmounted:

```javascript theme={null}
options.unmount = (vnode) => {
  // Cleanup before unmount
  console.log('Unmounting:', vnode.type);
};
```

**Type**: `(vnode: VNode) => void`

**Use cases**:

* Custom cleanup logic
* Track component lifecycle
* Remove external references
* Clear caches or subscriptions

Source: `src/index.d.ts:331`

**Example - Resource cleanup**:

```javascript theme={null}
const activeResources = new WeakMap();

options.vnode = (vnode) => {
  if (vnode.type === 'img') {
    const resource = { src: vnode.props.src };
    activeResources.set(vnode, resource);
  }
};

options.unmount = (vnode) => {
  const resource = activeResources.get(vnode);
  if (resource) {
    console.log('Cleaning up resource:', resource.src);
    activeResources.delete(vnode);
  }
};
```

### event

Transform or handle events before they're dispatched:

```javascript theme={null}
options.event = (event) => {
  // Modify event before dispatch
  console.log('Event:', event.type);
  return event;
};
```

**Type**: `(event: Event) => any`

**Use cases**:

* Event normalization
* Analytics tracking
* Custom event handling
* Polyfill event properties

Source: `src/index.d.ts:334`

**Example - Event analytics**:

```javascript theme={null}
options.event = (event) => {
  if (event.type === 'click') {
    analytics.track('click', {
      target: event.target.tagName,
      timestamp: Date.now()
    });
  }
  return event;
};
```

### requestAnimationFrame

Customize how Preact schedules effects:

```javascript theme={null}
options.requestAnimationFrame = (callback) => {
  // Custom scheduling
  setTimeout(callback, 16);
};
```

**Type**: `(callback: () => void) => void`

**Use cases**:

* Custom scheduling strategies
* Testing (synchronous effects)
* Server-side rendering
* Performance optimization

Source: `src/index.d.ts:335`

**Example - Synchronous effects for testing**:

```javascript theme={null}
// In tests
options.requestAnimationFrame = (callback) => {
  callback(); // Execute immediately instead of async
};
```

### debounceRendering

Control when state updates trigger re-renders:

```javascript theme={null}
options.debounceRendering = (callback) => {
  // Batch renders
  setTimeout(callback, 0);
};
```

**Type**: `(callback: () => void) => void`

**Use cases**:

* Batch multiple state updates
* Control render timing
* Testing (synchronous renders)
* Performance optimization

Source: `src/index.d.ts:336`

**Example - Synchronous rendering for tests**:

```javascript theme={null}
// Store original
const originalDebounce = options.debounceRendering;

// In tests, render synchronously
options.debounceRendering = (callback) => callback();

// Restore after tests
afterEach(() => {
  options.debounceRendering = originalDebounce;
});
```

### useDebugValue

Custom display for hook values in DevTools:

```javascript theme={null}
options.useDebugValue = (value) => {
  console.log('Hook value:', value);
};
```

**Type**: `(value: string | number) => void`

**Use cases**:

* Custom DevTools integration
* Debugging custom hooks
* Development logging

Source: `src/index.d.ts:337`

### \_addHookName

Add custom labels for hooks in DevTools:

```javascript theme={null}
options._addHookName = (name) => {
  // Called by addHookName() from preact/devtools
  console.log('Hook name:', name);
};
```

**Type**: `(name: string | number) => void`

Source: `src/index.d.ts:338`, `devtools/src/index.js:11-12`

### \_\_suspenseDidResolve

Callback when Suspense boundary resolves:

```javascript theme={null}
options.__suspenseDidResolve = (vnode, callback) => {
  console.log('Suspense resolved:', vnode);
  callback();
};
```

**Type**: `(vnode: VNode, callback: () => void) => void`

**Use cases**:

* Track Suspense states
* Custom loading behavior
* Analytics

Source: `src/index.d.ts:339`

## Internal Options Hooks

These hooks are used internally and by official addons. They're not officially public but are stable:

<Warning>
  Internal hooks may change between minor versions. Use with caution and test thoroughly when upgrading Preact.
</Warning>

### \_root

Invoked before rendering begins:

```javascript theme={null}
options._root = (vnode, parentNode) => {
  // Called by render() before starting
  console.log('Render starting');
};
```

**Type**: `(vnode: VNode, parentNode: Element) => void`

**Example from preact/debug**:

```javascript theme={null}
options._root = (vnode, parentNode) => {
  if (!parentNode) {
    throw new Error(
      'Undefined parent passed to render(), this is the second argument.'
    );
  }
  
  // Validate parent is a valid DOM node
  if (parentNode.nodeType !== 1 && 
      parentNode.nodeType !== 11 && 
      parentNode.nodeType !== 9) {
    throw new Error('Expected a valid HTML node as second argument');
  }
};
```

Source: `src/internal.d.ts:29`, `debug/src/debug.js:119-146`

### \_diff

Invoked before a vnode is diffed:

```javascript theme={null}
options._diff = (vnode) => {
  // Validate vnode before diffing
  if (vnode.type === undefined) {
    throw new Error('Undefined component passed to createElement()');
  }
};
```

**Type**: `(vnode: VNode) => void`

**Example from preact/debug**:

```javascript theme={null}
options._diff = (vnode) => {
  // Validate component type
  if (vnode.type === undefined) {
    throw new Error(
      'Undefined component passed to createElement()\n\n' +
      'You likely forgot to export your component or might have ' +
      'mixed up default and named imports'
    );
  }
  
  // Validate ref
  if (vnode.ref !== undefined && 
      typeof vnode.ref !== 'function' && 
      typeof vnode.ref !== 'object') {
    throw new Error(
      'Component\'s "ref" property should be a function or object'
    );
  }
};
```

Source: `src/internal.d.ts:31`, `debug/src/debug.js:148-245`

### \_render

Invoked before a component renders:

```javascript theme={null}
options._render = (vnode) => {
  // Called right before component function/render executes
  console.log('Rendering:', vnode.type);
};
```

**Type**: `(vnode: VNode) => void`

**Example - Infinite loop detection**:

```javascript theme={null}
let renderCount = 0;
let currentComponent;

options._render = (vnode) => {
  const nextComponent = vnode._component;
  if (nextComponent === currentComponent) {
    renderCount++;
  } else {
    renderCount = 1;
  }
  
  if (renderCount >= 25) {
    throw new Error('Too many re-renders. Infinite loop detected!');
  }
  
  currentComponent = nextComponent;
};
```

Source: `src/internal.d.ts:35`, `debug/src/debug.js:249-272`

### \_commit

Invoked after DOM updates are committed:

```javascript theme={null}
options._commit = (vnode, commitQueue) => {
  // Called after DOM is updated
  commitQueue.forEach(component => {
    console.log('Committed:', component);
  });
};
```

**Type**: `(vnode: VNode, commitQueue: Component[]) => void`

**Use cases**:

* Track DOM mutations
* Trigger side effects after paint
* Integration with other libraries

Source: `src/internal.d.ts:33`

### \_hook

Invoked when hooks are called:

```javascript theme={null}
import { HookType } from 'preact';

options._hook = (component, index, type) => {
  if (type === HookType.useState) {
    console.log('useState called at index', index);
  }
};
```

**Type**: `(component: Component, index: number, type: HookType) => void`

**Hook types**:

* `HookType.useState = 1`
* `HookType.useReducer = 2`
* `HookType.useEffect = 3`
* `HookType.useLayoutEffect = 4`
* `HookType.useRef = 5`
* `HookType.useImperativeHandle = 6`
* `HookType.useMemo = 7`
* `HookType.useCallback = 8`
* `HookType.useContext = 9`
* `HookType.useErrorBoundary = 10`
* `HookType.useDebugValue = 11`

Source: `src/internal.d.ts:3-16`, `src/internal.d.ts:37`

**Example - Hook validation**:

```javascript theme={null}
let hooksAllowed = false;

options._render = () => {
  hooksAllowed = true;
};

options.diffed = () => {
  hooksAllowed = false;
};

options._hook = (component, index, type) => {
  if (!hooksAllowed) {
    throw new Error('Hook can only be invoked from render methods.');
  }
};
```

Source: `debug/src/debug.js:59`, `debug/src/debug.js:274-280`

### \_catchError

Called when an error is caught:

```javascript theme={null}
options._catchError = (error, vnode, oldVNode, errorInfo) => {
  console.error('Error caught:', error);
  console.log('Component stack:', errorInfo.componentStack);
};
```

**Type**: `(error: any, vnode: VNode, oldVNode?: VNode, errorInfo?: ErrorInfo) => void`

**Example from preact/debug**:

```javascript theme={null}
options._catchError = (error, vnode, oldVNode, errorInfo) => {
  // Add component stack to error info
  errorInfo = errorInfo || {};
  errorInfo.componentStack = getOwnerStack(vnode);
  
  // Check for missing Suspense boundary
  if (typeof error.then === 'function') {
    let parent = vnode;
    let hasSuspense = false;
    
    for (; parent; parent = parent._parent) {
      if (parent._component && parent._component._childDidSuspend) {
        hasSuspense = true;
        break;
      }
    }
    
    if (!hasSuspense) {
      throw new Error(
        `Missing Suspense. The throwing component was: ${getDisplayName(vnode)}`
      );
    }
  }
};
```

Source: `src/internal.d.ts:41-46`, `debug/src/debug.js:78-117`

### \_hydrationMismatch

Called when hydration finds mismatched nodes:

```javascript theme={null}
options._hydrationMismatch = (vnode, excessDomChildren) => {
  const expected = vnode.type;
  const found = excessDomChildren
    .map(child => child && child.localName)
    .filter(Boolean);
  
  console.error(
    `Expected "${expected}" but found "${found.join(', ')}" during hydration`
  );
};
```

**Type**: `(vnode: VNode, excessDomChildren: Element[]) => void`

Source: `src/internal.d.ts:48-51`, `debug/src/debug.js:582-590`

## Chaining Options

When multiple libraries use the options API, they should chain previous hooks:

```javascript theme={null}
import { options } from 'preact';

// Save previous hook
const oldVNode = options.vnode;

// Install new hook that calls previous
options.vnode = (vnode) => {
  // Your logic
  console.log('My hook:', vnode.type);
  
  // Call previous hook
  if (oldVNode) oldVNode(vnode);
};
```

**Example from preact/debug**:

```javascript theme={null}
export function initDebug() {
  // Save all previous hooks
  let oldBeforeDiff = options._diff;
  let oldDiffed = options.diffed;
  let oldVnode = options.vnode;
  let oldRender = options._render;
  let oldCatchError = options._catchError;
  let oldRoot = options._root;
  let oldHook = options._hook;
  
  // Install new hooks that call previous ones
  options._diff = (vnode) => {
    // Debug logic here
    validateVNode(vnode);
    
    // Call previous hook
    if (oldBeforeDiff) oldBeforeDiff(vnode);
  };
  
  options.diffed = (vnode) => {
    // Debug logic here
    validateChildren(vnode);
    
    // Call previous hook
    if (oldDiffed) oldDiffed(vnode);
  };
  
  // ... more hooks
}
```

Source: `debug/src/debug.js:56-68`

<Warning>
  Always chain previous hooks to avoid breaking other libraries that use the options API.
</Warning>

## Complete Example: Custom Devtools

Here's a complete example building a simple custom devtools:

```javascript theme={null}
import { options } from 'preact';

class SimpleDevtools {
  constructor() {
    this.componentTree = [];
    this.renderTimes = new Map();
    this.init();
  }
  
  init() {
    // Chain existing hooks
    const oldVNode = options.vnode;
    const oldDiffed = options.diffed;
    const oldUnmount = options.unmount;
    
    // Track component creation
    options.vnode = (vnode) => {
      if (typeof vnode.type === 'function') {
        vnode.startTime = performance.now();
        this.componentTree.push({
          name: vnode.type.displayName || vnode.type.name,
          props: { ...vnode.props },
          created: Date.now()
        });
      }
      if (oldVNode) oldVNode(vnode);
    };
    
    // Track render completion
    options.diffed = (vnode) => {
      if (vnode.startTime) {
        const duration = performance.now() - vnode.startTime;
        const name = vnode.type.displayName || vnode.type.name;
        
        if (!this.renderTimes.has(name)) {
          this.renderTimes.set(name, []);
        }
        this.renderTimes.get(name).push(duration);
      }
      if (oldDiffed) oldDiffed(vnode);
    };
    
    // Track unmounts
    options.unmount = (vnode) => {
      if (typeof vnode.type === 'function') {
        console.log('Unmounted:', vnode.type.name);
      }
      if (oldUnmount) oldUnmount(vnode);
    };
  }
  
  getStats() {
    const stats = {};
    for (const [name, times] of this.renderTimes) {
      const avg = times.reduce((a, b) => a + b, 0) / times.length;
      stats[name] = {
        renders: times.length,
        avgTime: avg.toFixed(2),
        totalTime: times.reduce((a, b) => a + b, 0).toFixed(2)
      };
    }
    return stats;
  }
}

// Usage
const devtools = new SimpleDevtools();

// Later, get stats
console.table(devtools.getStats());
```

## Use Cases

The Options API enables:

* **Debug tools**: Validation and error reporting (preact/debug)
* **DevTools integration**: Component inspection (preact/devtools)
* **React compatibility**: React API emulation (preact/compat)
* **Performance monitoring**: Render tracking and profiling
* **Analytics**: User interaction tracking
* **Testing utilities**: Synchronous rendering (preact/test-utils)
* **Custom renderers**: Alternative render targets
* **State management**: External state integration

## Best Practices

1. **Always chain hooks**: Call previous hooks to support multiple libraries
2. **Minimize overhead**: Keep hook logic lightweight
3. **Clean up**: Remove hooks when done (restore previous values)
4. **TypeScript**: Use proper types from `preact/src/internal.d.ts`
5. **Test thoroughly**: Options hooks affect all components
6. **Document**: Make it clear your library uses options hooks
7. **Dev only**: Consider only enabling in development mode

## TypeScript Support

Extend the Options interface for custom hooks:

```typescript theme={null}
import 'preact';

declare module 'preact' {
  export interface Options {
    // Add your custom hook
    _customHook?: (vnode: VNode) => void;
  }
}

// Now TypeScript recognizes your custom hook
options._customHook = (vnode) => {
  console.log('Custom hook called');
};
```

## Performance Considerations

Options hooks are called frequently:

* `vnode`: Every element/component creation
* `_diff`: Before every diff
* `diffed`: After every render
* `_render`: Before every component render

Keep logic minimal to avoid performance impact:

```javascript theme={null}
// Bad: Expensive operation on every vnode
options.vnode = (vnode) => {
  JSON.stringify(vnode); // Slow!
  deepClone(vnode.props); // Slow!
};

// Good: Fast checks
options.vnode = (vnode) => {
  if (vnode.type === MyComponent) {
    // Only process specific components
  }
};
```
