Fast 3kb React-compatible Virtual DOM library.
—
Quality
Pending
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
Modern React hooks for state management, side effects, and component logic in functional components. Preact's hooks implementation provides complete compatibility with React hooks.
Hooks for managing local component state using functional programming patterns.
/**
* Manages local component state
* @param initialState - Initial state value or lazy initializer function
* @returns Tuple of current state and state setter function
*/
function useState<S>(initialState: S | (() => S)): [S, Dispatch<SetStateAction<S>>];
function useState<S = undefined>(): [S | undefined, Dispatch<SetStateAction<S | undefined>>];
/**
* Manages state using reducer pattern
* @param reducer - Reducer function that takes state and action
* @param initialArg - Initial state or argument for init function
* @param init - Optional lazy initializer function
* @returns Tuple of current state and dispatch function
*/
function useReducer<R extends Reducer<any, any>>(
reducer: R,
initialState: ReducerState<R>,
initializer?: undefined
): [ReducerState<R>, Dispatch<ReducerAction<R>>];
function useReducer<R extends Reducer<any, any>, I>(
reducer: R,
initialArg: I,
initializer: (arg: I) => ReducerState<R>
): [ReducerState<R>, Dispatch<ReducerAction<R>>];
// Supporting types
type Dispatch<A> = (value: A) => void;
type SetStateAction<S> = S | ((prevState: S) => S);
type Reducer<S, A> = (prevState: S, action: A) => S;
type ReducerState<R extends Reducer<any, any>> = R extends Reducer<infer S, any> ? S : never;
type ReducerAction<R extends Reducer<any, any>> = R extends Reducer<any, infer A> ? A : never;Usage Examples:
import { useState, useReducer, createElement } from "preact/hooks";
// Basic useState
function Counter() {
const [count, setCount] = useState(0);
return createElement("div", null,
createElement("p", null, `Count: ${count}`),
createElement("button", {
onClick: () => setCount(count + 1)
}, "Increment"),
createElement("button", {
onClick: () => setCount(prev => prev - 1)
}, "Decrement")
);
}
// useState with lazy initialization
function ExpensiveComponent() {
const [data, setData] = useState(() => {
// Expensive computation only runs once
return computeExpensiveValue();
});
return createElement("div", null, JSON.stringify(data));
}
// useReducer for complex state logic
interface State {
count: number;
step: number;
}
type Action =
| { type: 'increment' }
| { type: 'decrement' }
| { type: 'setStep'; step: number }
| { type: 'reset' };
function reducer(state: State, action: Action): State {
switch (action.type) {
case 'increment':
return { ...state, count: state.count + state.step };
case 'decrement':
return { ...state, count: state.count - state.step };
case 'setStep':
return { ...state, step: action.step };
case 'reset':
return { count: 0, step: 1 };
default:
return state;
}
}
function AdvancedCounter() {
const [state, dispatch] = useReducer(reducer, { count: 0, step: 1 });
return createElement("div", null,
createElement("p", null, `Count: ${state.count} (step: ${state.step})`),
createElement("button", {
onClick: () => dispatch({ type: 'increment' })
}, "Increment"),
createElement("button", {
onClick: () => dispatch({ type: 'decrement' })
}, "Decrement"),
createElement("input", {
type: "number",
value: state.step,
onChange: (e) => dispatch({
type: 'setStep',
step: parseInt((e.target as HTMLInputElement).value) || 1
})
}),
createElement("button", {
onClick: () => dispatch({ type: 'reset' })
}, "Reset")
);
}Hooks for managing side effects and lifecycle events in functional components.
/**
* Performs side effects after render
* @param effect - Effect function, optionally returns cleanup function
* @param deps - Optional dependency array to control when effect runs
*/
function useEffect(effect: EffectCallback, deps?: DependencyList): void;
/**
* Performs side effects synchronously before browser paint
* @param effect - Effect function, optionally returns cleanup function
* @param deps - Optional dependency array to control when effect runs
*/
function useLayoutEffect(effect: EffectCallback, deps?: DependencyList): void;
// Supporting types
type EffectCallback = () => (void | (() => void | undefined));
type DependencyList = ReadonlyArray<any>;Usage Examples:
import { useEffect, useLayoutEffect, useState, createElement } from "preact/hooks";
// Basic effect with cleanup
function Timer() {
const [time, setTime] = useState(new Date());
useEffect(() => {
const interval = setInterval(() => {
setTime(new Date());
}, 1000);
// Cleanup function
return () => clearInterval(interval);
}, []); // Empty deps array means effect runs once on mount
return createElement("div", null, time.toLocaleTimeString());
}
// Effect with dependencies
function UserProfile({ userId }: { userId: number }) {
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
let cancelled = false;
async function fetchUser() {
setLoading(true);
try {
const response = await fetch(`/api/users/${userId}`);
const userData = await response.json();
if (!cancelled) {
setUser(userData);
setLoading(false);
}
} catch (error) {
if (!cancelled) {
setUser(null);
setLoading(false);
}
}
}
fetchUser();
return () => {
cancelled = true;
};
}, [userId]); // Re-run when userId changes
if (loading) return createElement("div", null, "Loading...");
if (!user) return createElement("div", null, "User not found");
return createElement("div", null,
createElement("h1", null, user.name),
createElement("p", null, user.email)
);
}
// useLayoutEffect for DOM measurements
function MeasuredComponent() {
const [width, setWidth] = useState(0);
const [ref, setRef] = useState<HTMLDivElement | null>(null);
useLayoutEffect(() => {
if (ref) {
const updateWidth = () => {
setWidth(ref.offsetWidth);
};
updateWidth();
window.addEventListener('resize', updateWidth);
return () => {
window.removeEventListener('resize', updateWidth);
};
}
}, [ref]);
return createElement("div",
{ ref: setRef },
`Width: ${width}px`
);
}Hooks for creating persistent references and customizing ref behavior.
/**
* Creates a mutable ref object that persists across renders
* @param initialValue - Initial value for the ref
* @returns Mutable ref object with current property
*/
function useRef<T>(initialValue: T): MutableRefObject<T>;
function useRef<T>(initialValue: T | null): RefObject<T>;
function useRef<T = undefined>(): MutableRefObject<T | undefined>;
/**
* Customizes the instance value exposed by a ref
* @param ref - Ref to customize
* @param createHandle - Function that returns the custom instance
* @param deps - Optional dependency array
*/
function useImperativeHandle<T, R extends T>(
ref: Ref<T> | undefined,
init: () => R,
deps?: DependencyList
): void;
// Supporting types
interface MutableRefObject<T> {
current: T;
}
interface RefObject<T> {
readonly current: T | null;
}Usage Examples:
import { useRef, useImperativeHandle, forwardRef, createElement } from "preact/hooks";
// Basic ref usage
function TextInput() {
const inputRef = useRef<HTMLInputElement>(null);
const focusInput = () => {
if (inputRef.current) {
inputRef.current.focus();
}
};
return createElement("div", null,
createElement("input", { ref: inputRef, type: "text" }),
createElement("button", { onClick: focusInput }, "Focus Input")
);
}
// Ref for storing mutable values
function PreviousValue({ value }: { value: number }) {
const prevValueRef = useRef<number>();
useEffect(() => {
prevValueRef.current = value;
});
const prevValue = prevValueRef.current;
return createElement("div", null,
createElement("p", null, `Current: ${value}`),
createElement("p", null, `Previous: ${prevValue}`)
);
}
// Custom imperative handle
interface CustomInputHandle {
focus: () => void;
getValue: () => string;
setValue: (value: string) => void;
}
const CustomInput = forwardRef<CustomInputHandle, { placeholder?: string }>((props, ref) => {
const inputRef = useRef<HTMLInputElement>(null);
useImperativeHandle(ref, () => ({
focus: () => inputRef.current?.focus(),
getValue: () => inputRef.current?.value || '',
setValue: (value: string) => {
if (inputRef.current) {
inputRef.current.value = value;
}
}
}), []);
return createElement("input", {
ref: inputRef,
type: "text",
placeholder: props.placeholder
});
});
// Using the custom input
function ParentComponent() {
const customInputRef = useRef<CustomInputHandle>(null);
const handleButtonClick = () => {
if (customInputRef.current) {
customInputRef.current.focus();
customInputRef.current.setValue("Hello World");
}
};
return createElement("div", null,
createElement(CustomInput, { ref: customInputRef, placeholder: "Enter text" }),
createElement("button", { onClick: handleButtonClick }, "Set Value & Focus")
);
}Hooks for optimizing component performance through memoization.
/**
* Memoizes a computed value, recalculating only when dependencies change
* @param factory - Function that computes the memoized value
* @param deps - Dependency array that triggers recalculation
* @returns Memoized value
*/
function useMemo<T>(factory: () => T, deps: DependencyList): T;
/**
* Memoizes a callback function, recreating only when dependencies change
* @param callback - Function to memoize
* @param deps - Dependency array that triggers recreation
* @returns Memoized callback function
*/
function useCallback<T extends (...args: any[]) => any>(callback: T, deps: DependencyList): T;Usage Examples:
import { useMemo, useCallback, useState, createElement } from "preact/hooks";
// useMemo for expensive calculations
function ExpensiveList({ items }: { items: number[] }) {
const [filter, setFilter] = useState("");
const filteredAndSortedItems = useMemo(() => {
console.log("Recalculating filtered items"); // Only logs when items or filter change
return items
.filter(item => item.toString().includes(filter))
.sort((a, b) => a - b);
}, [items, filter]);
return createElement("div", null,
createElement("input", {
value: filter,
onChange: (e) => setFilter((e.target as HTMLInputElement).value),
placeholder: "Filter items"
}),
createElement("ul", null,
filteredAndSortedItems.map(item =>
createElement("li", { key: item }, item)
)
)
);
}
// useCallback for stable function references
interface TodoItemProps {
todo: { id: number; text: string; completed: boolean };
onToggle: (id: number) => void;
onDelete: (id: number) => void;
}
const TodoItem = ({ todo, onToggle, onDelete }: TodoItemProps) => {
return createElement("li", null,
createElement("input", {
type: "checkbox",
checked: todo.completed,
onChange: () => onToggle(todo.id)
}),
createElement("span", null, todo.text),
createElement("button", { onClick: () => onDelete(todo.id) }, "Delete")
);
};
function TodoList() {
const [todos, setTodos] = useState([
{ id: 1, text: "Learn Preact", completed: false },
{ id: 2, text: "Build an app", completed: false }
]);
// These callbacks won't cause TodoItem re-renders when todos change
const handleToggle = useCallback((id: number) => {
setTodos(prev => prev.map(todo =>
todo.id === id ? { ...todo, completed: !todo.completed } : todo
));
}, []);
const handleDelete = useCallback((id: number) => {
setTodos(prev => prev.filter(todo => todo.id !== id));
}, []);
return createElement("ul", null,
todos.map(todo =>
createElement(TodoItem, {
key: todo.id,
todo,
onToggle: handleToggle,
onDelete: handleDelete
})
)
);
}Additional hooks for context consumption, debugging, and error handling.
/**
* Consumes a context value from the nearest provider
* @param context - Context object created by createContext
* @returns Current context value
*/
function useContext<T>(context: Context<T>): T;
/**
* Generates a unique ID string for accessibility attributes
* @returns Unique string identifier
*/
function useId(): string;
/**
* Displays a custom label and value in React DevTools
* @param value - Value to display
* @param format - Optional formatter function
*/
function useDebugValue<T>(value: T): void;
function useDebugValue<T>(value: T, format: (value: T) => any): void;
/**
* Provides error boundary functionality for functional components
* @param onError - Optional error handler callback
* @returns Function to reset error state
*/
function useErrorBoundary(onError?: (error: Error, errorInfo: ErrorInfo) => void): (error?: Error) => void;Usage Examples:
import {
useContext,
useId,
useDebugValue,
useErrorBoundary,
createContext,
useState,
createElement
} from "preact/hooks";
// Context usage
const ThemeContext = createContext({ theme: 'light', toggleTheme: () => {} });
function ThemedButton() {
const { theme, toggleTheme } = useContext(ThemeContext);
return createElement("button", {
onClick: toggleTheme,
style: {
backgroundColor: theme === 'light' ? '#fff' : '#333',
color: theme === 'light' ? '#333' : '#fff'
}
}, `Current theme: ${theme}`);
}
// useId for accessibility
function FormField({ label }: { label: string }) {
const fieldId = useId();
const helpId = useId();
return createElement("div", null,
createElement("label", { htmlFor: fieldId }, label),
createElement("input", {
id: fieldId,
"aria-describedby": helpId
}),
createElement("div", { id: helpId }, "Help text for this field")
);
}
// Custom hook with debug value
function useLocalStorage<T>(key: string, initialValue: T) {
const [storedValue, setStoredValue] = useState<T>(() => {
try {
const item = window.localStorage.getItem(key);
return item ? JSON.parse(item) : initialValue;
} catch (error) {
return initialValue;
}
});
// Show the key and current value in DevTools
useDebugValue(`${key}: ${JSON.stringify(storedValue)}`);
const setValue = (value: T | ((val: T) => T)) => {
try {
const valueToStore = value instanceof Function ? value(storedValue) : value;
setStoredValue(valueToStore);
window.localStorage.setItem(key, JSON.stringify(valueToStore));
} catch (error) {
console.error(error);
}
};
return [storedValue, setValue] as const;
}
// Error boundary hook
function ErrorProneComponent({ shouldError }: { shouldError: boolean }) {
const resetError = useErrorBoundary((error, errorInfo) => {
console.error("Component error:", error, errorInfo);
});
if (shouldError) {
// This will trigger the error boundary
throw new Error("Something went wrong!");
}
return createElement("div", null,
createElement("p", null, "Component is working fine"),
createElement("button", { onClick: () => resetError() }, "Reset Error State")
);
}Install with Tessl CLI
npx tessl i tessl/npm-preact