CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-preact

Fast 3kb React-compatible Virtual DOM library.

Pending

Quality

Pending

Does it follow best practices?

Impact

Pending

No eval scenarios have been run

Overview
Eval results
Files

compat.mddocs/

React Compatibility

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.

Capabilities

Enhanced Components

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")
  );
}

Suspense and Lazy Loading

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)
      )
    )
  );
}

DOM Utilities

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")
    );
  }
}

Update Batching and Scheduling

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")
  );
}

Enhanced Children API

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"
    );
  }
}

React 18 Compatibility Hooks

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

docs

compat.md

components.md

context.md

core.md

devtools.md

hooks.md

index.md

jsx-runtime.md

testing.md

tile.json