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.
Preact has first-class TypeScript support with complete type definitions included in the core package. No additional @types packages are needed.
Setup
Preact’s TypeScript definitions are included by default. Just install Preact and start using TypeScript:
Create or update your tsconfig.json:
{
"compilerOptions": {
"target": "ES2020",
"module": "ESNext",
"jsx": "react-jsx",
"jsxImportSource": "preact",
"moduleResolution": "bundler",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"lib": ["ES2020", "DOM", "DOM.Iterable"]
},
"include": ["src/**/*"],
"exclude": ["node_modules"]
}
Create a .tsx file and start building:
import { render } from 'preact';
function App() {
return <h1>Hello TypeScript!</h1>;
}
render(<App />, document.getElementById('app')!);
Type Definitions
Preact’s type definitions are located in the source code:
src/index.d.ts - Core Preact types
src/jsx.d.ts - JSX type definitions
hooks/src/index.d.ts - Hooks type definitions
compat/src/index.d.ts - React compatibility types
Reference: src/index.d.ts, hooks/src/index.d.ts
Component Types
Function Components
Class Components
Function components are typed using the FunctionComponent type:import { FunctionComponent } from 'preact';
interface Props {
name: string;
age?: number;
}
const Greeting: FunctionComponent<Props> = ({ name, age }) => {
return (
<div>
<h1>Hello, {name}!</h1>
{age && <p>Age: {age}</p>}
</div>
);
};
Or use the shorthand (recommended):interface Props {
name: string;
age?: number;
}
function Greeting({ name, age }: Props) {
return (
<div>
<h1>Hello, {name}!</h1>
{age && <p>Age: {age}</p>}
</div>
);
}
Class components extend Component with props and state types:import { Component } from 'preact';
interface Props {
initialCount: number;
}
interface State {
count: number;
}
class Counter extends Component<Props, State> {
state: State = {
count: this.props.initialCount
};
increment = () => {
this.setState({ count: this.state.count + 1 });
};
render() {
return (
<div>
<p>Count: {this.state.count}</p>
<button onClick={this.increment}>Increment</button>
</div>
);
}
}
Props with Children
The ComponentChildren type represents valid children:
import { ComponentChildren } from 'preact';
interface Props {
title: string;
children: ComponentChildren;
}
function Card({ title, children }: Props) {
return (
<div className="card">
<h2>{title}</h2>
<div>{children}</div>
</div>
);
}
Alternatively, use FunctionComponent which includes children automatically:
import { FunctionComponent } from 'preact';
interface Props {
title: string;
}
// children is available automatically
const Card: FunctionComponent<Props> = ({ title, children }) => (
<div className="card">
<h2>{title}</h2>
<div>{children}</div>
</div>
);
Hooks with TypeScript
import { useState } from 'preact/hooks';
// Type is inferred
function Counter() {
const [count, setCount] = useState(0);
// count: number, setCount: (value: number) => void
return (
<button onClick={() => setCount(count + 1)}>
Count: {count}
</button>
);
}
// Explicit type
interface User {
name: string;
email: string;
}
function UserProfile() {
const [user, setUser] = useState<User | null>(null);
return (
<div>
{user ? <p>{user.name}</p> : <p>Loading...</p>}
</div>
);
}
Event Handlers
Preact provides specific event types for all DOM events:
import { JSX } from 'preact';
function Form() {
const handleSubmit = (e: JSX.TargetedEvent<HTMLFormElement, Event>) => {
e.preventDefault();
console.log('Form submitted');
};
const handleChange = (e: JSX.TargetedEvent<HTMLInputElement, Event>) => {
console.log('Input value:', e.currentTarget.value);
};
const handleClick = (e: JSX.TargetedMouseEvent<HTMLButtonElement>) => {
console.log('Button clicked at', e.clientX, e.clientY);
};
return (
<form onSubmit={handleSubmit}>
<input type="text" onChange={handleChange} />
<button onClick={handleClick}>Submit</button>
</form>
);
}
Common event types:
JSX.TargetedEvent<T, E> - Generic event
JSX.TargetedMouseEvent<T> - Mouse events
JSX.TargetedKeyboardEvent<T> - Keyboard events
JSX.TargetedFocusEvent<T> - Focus events
Refs with TypeScript
import { Component } from 'preact';
import { createRef } from 'preact';
class VideoPlayer extends Component {
play() {
console.log('Playing...');
}
pause() {
console.log('Paused');
}
}
class VideoController extends Component {
playerRef = createRef<VideoPlayer>();
handlePlay = () => {
this.playerRef.current?.play();
};
handlePause = () => {
this.playerRef.current?.pause();
};
render() {
return (
<div>
<VideoPlayer ref={this.playerRef} />
<button onClick={this.handlePlay}>Play</button>
<button onClick={this.handlePause}>Pause</button>
</div>
);
}
}
Context with TypeScript
import { createContext } from 'preact';
import { useContext, useState } from 'preact/hooks';
interface User {
id: string;
name: string;
email: string;
}
interface AuthContextValue {
user: User | null;
login: (email: string, password: string) => Promise<void>;
logout: () => void;
}
const AuthContext = createContext<AuthContextValue | undefined>(undefined);
export function AuthProvider({ children }: { children: ComponentChildren }) {
const [user, setUser] = useState<User | null>(null);
const login = async (email: string, password: string) => {
const response = await fetch('/api/login', {
method: 'POST',
body: JSON.stringify({ email, password })
});
const userData = await response.json();
setUser(userData);
};
const logout = () => {
setUser(null);
};
return (
<AuthContext.Provider value={{ user, login, logout }}>
{children}
</AuthContext.Provider>
);
}
export function useAuth() {
const context = useContext(AuthContext);
if (!context) {
throw new Error('useAuth must be used within AuthProvider');
}
return context;
}
Custom Hooks
Type your custom hooks like regular functions:
import { useState, useEffect } from 'preact/hooks';
interface FetchState<T> {
data: T | null;
loading: boolean;
error: Error | null;
}
function useFetch<T>(url: string): FetchState<T> {
const [state, setState] = useState<FetchState<T>>({
data: null,
loading: true,
error: null
});
useEffect(() => {
let cancelled = false;
fetch(url)
.then(res => res.json())
.then(data => {
if (!cancelled) {
setState({ data, loading: false, error: null });
}
})
.catch(error => {
if (!cancelled) {
setState({ data: null, loading: false, error });
}
});
return () => {
cancelled = true;
};
}, [url]);
return state;
}
// Usage
interface User {
id: number;
name: string;
}
function UserProfile({ userId }: { userId: number }) {
const { data, loading, error } = useFetch<User>(
`/api/users/${userId}`
);
if (loading) return <div>Loading...</div>;
if (error) return <div>Error: {error.message}</div>;
if (!data) return <div>No data</div>;
return <div>{data.name}</div>;
}
Generic Components
Create reusable components that work with any type:
import { ComponentChildren } from 'preact';
interface ListProps<T> {
items: T[];
renderItem: (item: T) => ComponentChildren;
keyExtractor: (item: T) => string | number;
}
function List<T>({ items, renderItem, keyExtractor }: ListProps<T>) {
return (
<ul>
{items.map(item => (
<li key={keyExtractor(item)}>
{renderItem(item)}
</li>
))}
</ul>
);
}
// Usage
interface User {
id: number;
name: string;
}
const users: User[] = [
{ id: 1, name: 'Alice' },
{ id: 2, name: 'Bob' }
];
function App() {
return (
<List
items={users}
renderItem={user => <span>{user.name}</span>}
keyExtractor={user => user.id}
/>
);
}
Utility Types
Preact exports useful utility types:
import {
ComponentChildren,
ComponentType,
VNode,
RefObject,
ComponentProps
} from 'preact';
// Get props type from a component
const Button: FunctionComponent<{ label: string }> = ({ label }) => (
<button>{label}</button>
);
type ButtonProps = ComponentProps<typeof Button>;
// { label: string; children?: ComponentChildren }
// RefObject type
const ref: RefObject<HTMLDivElement> = { current: null };
// VNode type
const vnode: VNode = <div>Hello</div>;
Best Practices
{
"compilerOptions": {
"strict": true,
"strictNullChecks": true,
"noImplicitAny": true
}
}
Use inference when possible
// Good: Type is inferred
const [count, setCount] = useState(0);
// Unnecessary: Type already inferred
const [count, setCount] = useState<number>(0);
// Necessary: Complex type or null initial value
const [user, setUser] = useState<User | null>(null);
Create interfaces for props
// Good: Reusable interface
interface ButtonProps {
label: string;
onClick: () => void;
variant?: 'primary' | 'secondary';
}
function Button({ label, onClick, variant = 'primary' }: ButtonProps) {
return (
<button className={variant} onClick={onClick}>
{label}
</button>
);
}
Use discriminated unions for state
type AsyncState<T> =
| { status: 'idle' }
| { status: 'loading' }
| { status: 'success'; data: T }
| { status: 'error'; error: Error };
function UserProfile() {
const [state, setState] = useState<AsyncState<User>>({
status: 'idle'
});
if (state.status === 'loading') return <div>Loading...</div>;
if (state.status === 'error') return <div>{state.error.message}</div>;
if (state.status === 'success') return <div>{state.data.name}</div>;
return null;
}
Next Steps
Hooks
Learn about hooks with full TypeScript support
Context
Type-safe context with TypeScript