Fast 3kb React-compatible Virtual DOM library.
—
Quality
Pending
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
Complete React compatibility layer enabling seamless migration from React applications with advanced features like memo, forwardRef, Suspense, and portals. The preact/compat module provides a drop-in replacement for React.
Advanced component patterns and higher-order components for performance optimization and component composition.
/**
* React version compatibility string
*/
const version: string; // "18.3.1"
/**
* No-op component that passes through children (alias for Fragment)
*/
const StrictMode: FunctionComponent<{ children?: ComponentChildren }>;
/**
* Component with automatic shallow comparison of props and state
*/
abstract class PureComponent<P = {}, S = {}> extends Component<P, S> {
// Automatically implements shouldComponentUpdate with shallow comparison
}
/**
* Higher-order component that memoizes component rendering
* @param component - Component to memoize
* @param propsAreEqual - Optional custom comparison function
* @returns Memoized component
*/
function memo<P extends object>(
component: FunctionComponent<P>,
propsAreEqual?: (prevProps: Readonly<P>, nextProps: Readonly<P>) => boolean
): FunctionComponent<P>;
/**
* Forwards refs to child components
* @param render - Render function that receives props and ref
* @returns Component that forwards refs
*/
function forwardRef<T, P = {}>(
render: (props: P, ref: Ref<T>) => ComponentChildren
): ComponentType<P & RefAttributes<T>>;
interface RefAttributes<T> {
ref?: Ref<T>;
}Usage Examples:
import { PureComponent, memo, forwardRef, createElement } from "preact/compat";
// PureComponent automatically optimizes re-renders
class UserCard extends PureComponent<{ user: { name: string; email: string } }> {
render() {
const { user } = this.props;
return createElement("div", { className: "user-card" },
createElement("h3", null, user.name),
createElement("p", null, user.email)
);
}
// No need to implement shouldComponentUpdate - automatically does shallow comparison
}
// Memoized functional component
const ExpensiveComponent = memo<{ data: any[]; processing?: boolean }>(
({ data, processing }) => {
const processedData = data.map(item => ({ ...item, processed: true }));
return createElement("div", null,
processing && createElement("div", null, "Processing..."),
createElement("ul", null,
processedData.map((item, index) =>
createElement("li", { key: index }, JSON.stringify(item))
)
)
);
},
// Custom comparison function
(prevProps, nextProps) => {
return prevProps.data.length === nextProps.data.length &&
prevProps.processing === nextProps.processing;
}
);
// ForwardRef for exposing child component methods
interface InputHandle {
focus(): void;
getValue(): string;
}
const FancyInput = forwardRef<InputHandle, { placeholder?: string }>((props, ref) => {
const inputRef = useRef<HTMLInputElement>(null);
useImperativeHandle(ref, () => ({
focus: () => inputRef.current?.focus(),
getValue: () => inputRef.current?.value || ''
}));
return createElement("input", {
ref: inputRef,
placeholder: props.placeholder,
className: "fancy-input"
});
});
// Using forwardRef component
function ParentComponent() {
const inputRef = useRef<InputHandle>(null);
return createElement("div", null,
createElement(FancyInput, { ref: inputRef, placeholder: "Enter text" }),
createElement("button", {
onClick: () => {
inputRef.current?.focus();
console.log(inputRef.current?.getValue());
}
}, "Focus & Log Value")
);
}Components and utilities for handling asynchronous loading and code splitting.
/**
* Lazy-loaded component wrapper
* @param factory - Function that returns a Promise resolving to a component
* @returns Lazy component that can be used with Suspense
*/
function lazy<P extends ComponentProps<any>>(
factory: () => Promise<{ default: ComponentType<P> }>
): ComponentType<P>;
/**
* Suspense boundary for handling loading states
*/
const Suspense: ComponentType<{
children: ComponentChildren;
fallback?: ComponentChildren;
}>;
/**
* Experimental component for coordinating multiple Suspense boundaries
*/
const SuspenseList: ComponentType<{
children: ComponentChildren;
revealOrder?: "forwards" | "backwards" | "together";
tail?: "collapsed" | "hidden";
}>;Usage Examples:
import { lazy, Suspense, createElement } from "preact/compat";
// Lazy loaded components
const LazyUserProfile = lazy(() => import("./UserProfile"));
const LazyDashboard = lazy(() => import("./Dashboard"));
function App() {
const [currentView, setCurrentView] = useState("profile");
return createElement("div", null,
createElement("nav", null,
createElement("button", {
onClick: () => setCurrentView("profile")
}, "Profile"),
createElement("button", {
onClick: () => setCurrentView("dashboard")
}, "Dashboard")
),
createElement(Suspense, {
fallback: createElement("div", null, "Loading...")
},
currentView === "profile"
? createElement(LazyUserProfile, { userId: 123 })
: createElement(LazyDashboard)
)
);
}
// Multiple lazy components with SuspenseList
function MultiComponentView() {
return createElement(Suspense, {
fallback: createElement("div", null, "Loading all components...")
},
createElement(SuspenseList, { revealOrder: "forwards" },
createElement(Suspense, {
fallback: createElement("div", null, "Loading header...")
},
createElement(LazyHeader)
),
createElement(Suspense, {
fallback: createElement("div", null, "Loading content...")
},
createElement(LazyContent)
),
createElement(Suspense, {
fallback: createElement("div", null, "Loading footer...")
},
createElement(LazyFooter)
)
)
);
}Utilities for DOM manipulation and component lifecycle management.
/**
* Renders components into a different DOM subtree
* @param children - Components to render
* @param container - DOM element to render into
* @returns Portal VNode
*/
function createPortal(children: ComponentChildren, container: Element): VNode;
/**
* Removes a component tree from a DOM container
* @param container - DOM container to unmount from
* @returns True if component was unmounted
*/
function unmountComponentAtNode(container: Element): boolean;
/**
* Gets the DOM node for a component instance
* @param component - Component instance or DOM element
* @returns DOM element or null
*/
function findDOMNode(component: Component<any> | Element | null): Element | null;
/**
* Creates a factory function for creating elements of a specific type (legacy)
* @param type - Component or element type
* @returns Factory function that creates elements of the specified type
*/
function createFactory<P>(type: ComponentType<P> | string): (props?: P, ...children: ComponentChildren[]) => VNode<P>;Usage Examples:
import { createPortal, unmountComponentAtNode, findDOMNode, createElement } from "preact/compat";
// Portal for rendering modals
function Modal({ isOpen, onClose, children }: {
isOpen: boolean;
onClose: () => void;
children: ComponentChildren;
}) {
if (!isOpen) return null;
const modalRoot = document.getElementById("modal-root")!;
return createPortal(
createElement("div", { className: "modal-overlay", onClick: onClose },
createElement("div", {
className: "modal-content",
onClick: (e) => e.stopPropagation()
},
createElement("button", {
className: "modal-close",
onClick: onClose
}, "×"),
children
)
),
modalRoot
);
}
// Using the modal
function App() {
const [isModalOpen, setIsModalOpen] = useState(false);
return createElement("div", null,
createElement("button", {
onClick: () => setIsModalOpen(true)
}, "Open Modal"),
createElement(Modal, {
isOpen: isModalOpen,
onClose: () => setIsModalOpen(false)
},
createElement("h2", null, "Modal Content"),
createElement("p", null, "This is rendered in a portal!")
)
);
}
// Cleanup utility
function ComponentManager() {
const containerRef = useRef<HTMLDivElement>(null);
const cleanupComponent = () => {
if (containerRef.current) {
const wasUnmounted = unmountComponentAtNode(containerRef.current);
console.log("Component unmounted:", wasUnmounted);
}
};
return createElement("div", null,
createElement("div", { ref: containerRef }),
createElement("button", { onClick: cleanupComponent }, "Cleanup")
);
}
// findDOMNode usage (use with caution in modern code)
class LegacyComponent extends Component {
focusInput() {
const domNode = findDOMNode(this);
if (domNode instanceof Element) {
const input = domNode.querySelector("input");
input?.focus();
}
}
render() {
return createElement("div", null,
createElement("input", { type: "text" }),
createElement("button", {
onClick: () => this.focusInput()
}, "Focus Input")
);
}
}Functions for controlling update timing and batching behavior.
/**
* Synchronously flushes updates (no-op in Preact)
* @param callback - Function to execute synchronously
* @returns The return value of the callback
*/
function flushSync<R>(callback: () => R): R;
/**
* Manually batches state updates
* @param callback - Function containing state updates to batch
*/
function unstable_batchedUpdates(callback: () => void): void;
/**
* Marks updates as non-urgent (no-op in Preact)
* @param callback - Function containing non-urgent updates
*/
function startTransition(callback: () => void): void;Usage Examples:
import { flushSync, unstable_batchedUpdates, startTransition, useState, createElement } from "preact/compat";
function BatchingExample() {
const [count, setCount] = useState(0);
const [name, setName] = useState("");
const handleMultipleUpdates = () => {
// These updates are automatically batched in modern Preact
setCount(c => c + 1);
setName("Updated");
// Force synchronous update (rarely needed)
flushSync(() => {
setCount(c => c + 1);
});
// Explicit batching (mostly for compatibility)
unstable_batchedUpdates(() => {
setCount(c => c + 1);
setName("Batched Update");
});
// Non-urgent updates (no-op in Preact)
startTransition(() => {
setCount(c => c + 1);
});
};
return createElement("div", null,
createElement("p", null, `Count: ${count}`),
createElement("p", null, `Name: ${name}`),
createElement("button", { onClick: handleMultipleUpdates }, "Update All")
);
}Extended utilities for manipulating and working with component children.
/**
* Enhanced children utilities
*/
const Children: {
/**
* Maps over children with a function
* @param children - Children to map over
* @param fn - Function to apply to each child
* @returns Array of mapped children
*/
map(children: ComponentChildren, fn: (child: ComponentChild, index: number) => ComponentChild): ComponentChild[];
/**
* Iterates over children with a function
* @param children - Children to iterate over
* @param fn - Function to call for each child
*/
forEach(children: ComponentChildren, fn: (child: ComponentChild, index: number) => void): void;
/**
* Counts the number of children
* @param children - Children to count
* @returns Number of children
*/
count(children: ComponentChildren): number;
/**
* Ensures children contains exactly one child
* @param children - Children to validate
* @returns The single child
* @throws Error if not exactly one child
*/
only(children: ComponentChildren): ComponentChild;
/**
* Converts children to an array
* @param children - Children to convert
* @returns Array of children
*/
toArray(children: ComponentChildren): ComponentChild[];
};
/**
* Additional element type checking utilities
*/
function isValidElement(element: any): element is VNode;
function isFragment(element: any): boolean;
function isMemo(element: any): boolean;Usage Examples:
import { Children, isValidElement, isFragment, createElement } from "preact/compat";
// Using Children utilities
function ChildrenProcessor({ children }: { children: ComponentChildren }) {
const childCount = Children.count(children);
const processedChildren = Children.map(children, (child, index) => {
if (isValidElement(child)) {
// Add index prop to valid elements
return createElement(child.type, {
...child.props,
key: child.key || index,
"data-index": index
});
}
return child;
});
return createElement("div", null,
createElement("p", null, `Processing ${childCount} children`),
processedChildren
);
}
// Validation utilities
function SafeContainer({ children }: { children: ComponentChildren }) {
Children.forEach(children, (child, index) => {
if (isValidElement(child)) {
console.log(`Child ${index} is a valid element:`, child.type);
} else if (isFragment(child)) {
console.log(`Child ${index} is a fragment`);
} else {
console.log(`Child ${index} is primitive:`, child);
}
});
return createElement("div", null, children);
}
// Single child validation
function SingleChildWrapper({ children }: { children: ComponentChildren }) {
try {
const singleChild = Children.only(children);
return createElement("div", { className: "single-wrapper" }, singleChild);
} catch (error) {
return createElement("div", { className: "error" },
"This component requires exactly one child"
);
}
}Modern React 18 hooks for concurrent features and external store synchronization.
/**
* Insertion effect that runs before layout effects (alias for useLayoutEffect)
* @param effect - Effect function
* @param deps - Dependency array
*/
function useInsertionEffect(effect: EffectCallback, deps?: DependencyList): void;
/**
* Returns transition state and function for non-urgent updates
* @returns Tuple of isPending (always false) and startTransition function
*/
function useTransition(): [false, typeof startTransition];
/**
* Returns a deferred value for performance optimization (pass-through in Preact)
* @param value - Value to defer
* @returns The same value (no deferring in Preact)
*/
function useDeferredValue<T>(value: T): T;
/**
* Synchronizes with external store state
* @param subscribe - Function to subscribe to store changes
* @param getSnapshot - Function to get current store state
* @param getServerSnapshot - Optional server-side snapshot function
* @returns Current store state
*/
function useSyncExternalStore<T>(
subscribe: (onStoreChange: () => void) => () => void,
getSnapshot: () => T,
getServerSnapshot?: () => T
): T;Usage Examples:
import {
useInsertionEffect,
useTransition,
useDeferredValue,
useSyncExternalStore,
useState,
createElement
} from "preact/compat";
// useInsertionEffect for critical DOM mutations
function CriticalStyleInjector() {
useInsertionEffect(() => {
// Insert critical styles before any layout effects
const style = document.createElement('style');
style.textContent = '.critical { color: red; }';
document.head.appendChild(style);
return () => {
document.head.removeChild(style);
};
}, []);
return createElement("div", { className: "critical" }, "Critical content");
}
// useTransition for non-urgent updates
function TransitionExample() {
const [query, setQuery] = useState("");
const [results, setResults] = useState([]);
const [isPending, startTransition] = useTransition();
const handleSearch = (e: Event) => {
const value = (e.target as HTMLInputElement).value;
setQuery(value);
// Mark expensive search as non-urgent
startTransition(() => {
// Expensive search operation
const searchResults = performExpensiveSearch(value);
setResults(searchResults);
});
};
return createElement("div", null,
createElement("input", {
value: query,
onChange: handleSearch,
placeholder: "Search..."
}),
isPending && createElement("div", null, "Searching..."),
createElement("ul", null,
results.map((result, index) =>
createElement("li", { key: index }, result)
)
)
);
}
// useDeferredValue for performance optimization
function DeferredExample() {
const [input, setInput] = useState("");
const deferredInput = useDeferredValue(input);
return createElement("div", null,
createElement("input", {
value: input,
onChange: (e) => setInput((e.target as HTMLInputElement).value)
}),
createElement(ExpensiveComponent, { query: deferredInput })
);
}
// useSyncExternalStore for external state
function ExternalStoreExample() {
// External store (e.g., a simple observable)
const store = {
state: { count: 0 },
listeners: new Set<() => void>(),
subscribe(listener: () => void) {
this.listeners.add(listener);
return () => this.listeners.delete(listener);
},
getSnapshot() {
return this.state;
},
increment() {
this.state = { count: this.state.count + 1 };
this.listeners.forEach(listener => listener());
}
};
const state = useSyncExternalStore(
store.subscribe.bind(store),
store.getSnapshot.bind(store)
);
return createElement("div", null,
createElement("p", null, `External count: ${state.count}`),
createElement("button", {
onClick: () => store.increment()
}, "Increment External Store")
);
}Install with Tessl CLI
npx tessl i tessl/npm-preact