UNPKG

react-rx-signals

Version:

SolidJS-style signals and stores for React using RxJS - 98% fewer re-renders with fine-grained reactivity

595 lines (579 loc) 23 kB
import { Observable } from 'rxjs'; import React, { DependencyList } from 'react'; /** * Creates a highly optimized signal with maximum performance and comprehensive error handling * - Zero-allocation fast paths for identical values * - Pre-bound methods to avoid .bind() overhead * - Optimized function type checking * - Cached observable pipeline * - Comprehensive error handling with fallbacks * - Memory leak prevention */ declare function createSignal<T>(initial: T): readonly [() => T, (value: T | ((prev: T) => T)) => void, Observable<T>]; /** * Creates a highly optimized computed observable with advanced caching and error handling * - WeakRef-based memory management * - LRU cache for frequently used computations * - Fast cache key generation * - Automatic cleanup of stale references * - Comprehensive error handling with fallbacks */ declare function createComputed<T, U>(source$: Observable<T>, compute: (value: T) => U): Observable<U>; /** * Advanced signal creation with performance monitoring (development only) */ declare function createSignalWithMetrics<T>(initial: T, name?: string): readonly [() => T, (value: T | ((prev: T) => T)) => void, Observable<T>]; /** * Batch signal updates for maximum performance * Groups multiple signal updates into a single microtask */ declare function batchSignalUpdates(updates: (() => void)[]): void; /** * Create multiple signals efficiently with shared configuration */ declare function createSignals<T extends Record<string, unknown>>(initialValues: T): { [K in keyof T]: readonly [ () => T[K], (value: T[K] | ((prev: T[K]) => T[K])) => void, Observable<T[K]> ]; }; type Updater<T> = (prev: T) => T; /** * Creates a highly optimized store with maximum performance and comprehensive error handling * - Ultra-fast shallow equality checking * - Advanced LRU caching for selectors * - Optimized object merging strategies * - Pooled setter functions for reuse * - Pre-compiled observable pipelines * - Comprehensive error handling with fallbacks * - Memory leak prevention */ declare function createStore<T extends object>(initial: T): readonly [() => T, (update: Partial<T> | Updater<T>) => void, Observable<T>, <K extends keyof T>(key: K) => Observable<T[K]>]; /** * Advanced store creation with performance monitoring (development only) */ declare function createStoreWithMetrics<T extends object>(initial: T, name?: string): readonly [() => T, (update: Partial<T> | Updater<T>) => void, Observable<T>, <K extends keyof T>(key: K) => Observable<T[K]>]; /** * Batch store updates for maximum performance * Groups multiple store updates into a single microtask */ declare function batchStoreUpdates(updates: (() => void)[]): void; /** * Create multiple stores efficiently with shared configuration */ declare function createStores<T extends Record<string, object>>(initialValues: T): { [K in keyof T]: readonly [ () => T[K], (update: Partial<T[K]> | Updater<T[K]>) => void, Observable<T[K]>, <P extends keyof T[K]>(key: P) => Observable<T[K][P]> ]; }; /** * Advanced selector with deep path support and caching */ declare function createDeepSelector<T extends object, R>(store: Observable<T>, path: string[], transform?: (value: unknown) => R): Observable<R>; /** * Optimized store state comparison utility */ declare function compareStoreStates<T extends object>(prev: T, next: T, keys?: (keyof T)[]): boolean; /** * Simplified and safe useSignal hook with RxJS optimizations * - distinctUntilChanged to prevent repetitive values at source level * - takeWhile for conditional subscription termination * - take(1) for initial value fetching * - Fast path optimizations with Object.is comparison * - Safe error handling with fallbacks * * @example Basic usage: * ```typescript * const count = useSignal(counter$, 0); * ``` * * @example With condition (stops when count reaches 10): * ```typescript * const count = useSignal(counter$, 0, { * condition: (value) => value < 10 * }); * ``` * * @example Using utility conditions: * ```typescript * // Stop when value becomes falsy * const value = useSignal(data$, '', { * condition: SignalConditions.whileTruthy * }); * * // Stop when value goes above 100 * const score = useSignal(score$, 0, { * condition: SignalConditions.whileBelow(100) * }); * * // Take only first 5 values * const limited = useSignal(stream$, null, { * condition: SignalConditions.takeCount(5) * }); * ``` * * @param source$ - The observable to subscribe to * @param initial - Initial value to use * @param options - Optional configuration * @param options.condition - Function to control subscription continuation */ declare function useSignal<T>(source$: Observable<T>, initial: T, options?: { condition?: (value: T) => boolean; }): T; /** * Performance utility: No-op for API compatibility */ declare function clearSubscriptionPool(): void; /** * Performance utility: Get subscription statistics */ declare function getSubscriptionPoolStats(): { cacheSize: string; note: string; }; /** * Utility functions for common takeWhile conditions */ declare const SignalConditions: { /** * Take values while they are truthy */ readonly whileTruthy: <T>(value: T) => boolean; /** * Take values while they are below a threshold */ readonly whileBelow: (threshold: number) => (value: number) => boolean; /** * Take values while they are above a threshold */ readonly whileAbove: (threshold: number) => (value: number) => boolean; /** * Take values while they match a condition */ readonly whileMatches: <T>(predicate: (value: T) => boolean) => (value: T) => boolean; /** * Take values for a specific count */ readonly takeCount: (count: number) => <T>(_value: T) => boolean; }; /** * Enhanced useStore hook with RxJS optimizations * - distinctUntilChanged to prevent repetitive values at source level * - takeWhile for conditional subscription termination * - take(1) for initial value fetching * - Fast shallow equality for objects, Object.is for primitives * - Safe error handling with fallbacks * * @example Basic usage: * ```typescript * const user = useStore(userStore$, { id: 0, name: '' }); * ``` * * @example With condition (stops when user becomes inactive): * ```typescript * const user = useStore(userStore$, { id: 0, name: '', active: true }, { * condition: (user) => user.active * }); * ``` * * @example Using utility conditions: * ```typescript * // Stop when store becomes falsy * const data = useStore(dataStore$, null, { * condition: StoreConditions.whileTruthy * }); * * // Stop when specific property changes * const state = useStore(appState$, initialState, { * condition: StoreConditions.whilePropertyEquals('status', 'loading') * }); * ``` * * @param source$ - The observable store to subscribe to * @param initial - Initial value to use * @param options - Optional configuration * @param options.condition - Function to control subscription continuation */ declare function useStore<T>(source$: Observable<T>, initial: T, options?: { condition?: (value: T) => boolean; }): T; /** * Utility functions for common store takeWhile conditions */ declare const StoreConditions: { /** * Take values while they are truthy */ readonly whileTruthy: <T>(value: T) => boolean; /** * Take values while a specific property equals a value */ readonly whilePropertyEquals: <T extends Record<string, any>, K extends keyof T>(property: K, expectedValue: T[K]) => (value: T) => boolean; /** * Take values while a specific property is truthy */ readonly whilePropertyTruthy: <T extends Record<string, any>, K extends keyof T>(property: K) => (value: T) => boolean; /** * Take values while all specified properties are truthy */ readonly whilePropertiesTruthy: <T extends Record<string, any>>(...properties: (keyof T)[]) => (value: T) => boolean; /** * Take values while store matches a condition */ readonly whileMatches: <T>(predicate: (value: T) => boolean) => (value: T) => boolean; /** * Take values for a specific count */ readonly takeCount: (count: number) => <T>(_value: T) => boolean; /** * Take values while store has a specific structure */ readonly whileHasProperty: <T extends Record<string, any>>(property: keyof T) => (value: T) => boolean; }; /** * Track lifecycle of a signal or store field * @param source$ - RxJS Observable (signal or store selector) * @param get - function to get the current value * @param callback - function called with (prev, current) on each update */ declare function useSignalLifecycle<T>(source$: Observable<T>, get: () => T, callback: (prev: T | undefined, current: T) => void): void; /** * Hook that runs an effect when signal values change * Alternative to useEffect that works seamlessly with signals * * @param source$ - RxJS Observable to subscribe to * @param effect - Effect function that runs when signal changes * @param deps - Optional dependency list for effect updates */ declare function useSignalEffect<T>(source$: Observable<T>, effect: (value: T) => void | (() => void), deps?: DependencyList): void; /** * Hook that provides the current value of an observable for use in useEffect dependencies * Solves the issue where signals don't trigger useEffect when used directly * * @param source$ - RxJS Observable to subscribe to * @param initial - Initial value before first emission * @returns Current value of the observable */ declare function useSignalValue<T>(source$: Observable<T>, initial: T): T; /** * Hook that provides a callback to manually trigger effects based on signal state * Useful for imperative operations that need to respond to signal changes * * @param source$ - RxJS Observable to track * @param callback - Function to call with current signal value * @returns Function that when called, executes callback with current signal value */ declare function useSignalCallback<T, R>(source$: Observable<T>, callback: (value: T) => R): () => R; /** * Hook that debounces signal changes for use in effects * Prevents excessive effect executions when signals change rapidly * * @param source$ - RxJS Observable to subscribe to * @param effect - Effect function that runs after debounce delay * @param delay - Debounce delay in milliseconds * @param deps - Optional dependency list for effect updates */ declare function useDebouncedSignalEffect<T>(source$: Observable<T>, effect: (value: T) => void | (() => void), delay: number, deps?: DependencyList): void; /** * Hook that automatically memoizes all children to prevent unnecessary re-renders * Perfect for components that use signals but want to prevent child re-renders * * @example * ```tsx * function Counter() { * const count = useSignal(count$, 0); * const memoizeChildren = useAutoMemo(); * * return ( * <div> * <p>Count: {count}</p> * <button onClick={() => setCount(c => c + 1)}>Increment</button> * {memoizeChildren(<Child />)} * {memoizeChildren(<AnotherChild data="static" />)} * </div> * ); * } * ``` */ declare function useAutoMemo(): (element: React.ReactElement) => React.ReactElement; /** * Decorator to automatically optimize child components * Use this around components that use signals to prevent child re-renders * * @example * ```tsx * const OptimizedCounter = withAutoMemo(function Counter() { * const count = useSignal(count$, 0); * * return ( * <div> * <p>Count: {count}</p> * <Child /> * <AnotherChild /> * </div> * ); * }); * ``` */ declare function withAutoMemo<T extends Record<string, unknown>>(Component: React.ComponentType<T>): React.ComponentType<T>; /** * Hook to create a static component that never re-renders * Perfect for components with no dependencies * * @example * ```tsx * function Parent() { * const count = useSignal(count$, 0); * const StaticChild = useStaticMemo(() => * React.createElement('div', {}, 'I never re-render!') * ); * * return React.createElement('div', {}, * React.createElement('p', {}, 'Count: ', count), * React.createElement(StaticChild) * ); * } * ``` */ declare function useStaticMemo<T extends Record<string, unknown>>(renderFn: () => React.ReactElement<T>): React.ComponentType<T>; /** * useSignalTextProps * Return DOM attributes to bind an element's textContent directly to an Observable * No refs, no effects in the hook; global MutationObserver manages lifecycle */ declare function useSignalTextProps<T>(source$: Observable<T>, initialValue: T, transform?: (value: T) => string): Record<string, string>; /** * TRUE fine-grained hook that updates DOM directly without component re-renders * Returns a ref callback to attach to DOM elements for direct updates * ✅ Zero re-renders - children never re-render * * @example * // ✅ Zero re-renders - children never re-render * const countRef = useTrueFineGrainedValue(count$, 0); * // Use ref={countRef} on a span/div to display the reactive value */ declare function useTrueFineGrainedValue<T>(source$: Observable<T>, initialValue: T, transform?: (value: T) => string): (element: HTMLElement | null) => void; /** * Component that renders reactive text with direct DOM updates * No component re-renders, just DOM text updates * * @example * // Zero re-renders with direct DOM text updates * React.createElement(FineGrainedText, { source: count$ }) */ declare function FineGrainedText<T>({ source, transform, className, style, ...props }: { source: Observable<T>; transform?: (value: T) => string; className?: string; style?: React.CSSProperties; [key: string]: unknown; }): React.DetailedReactHTMLElement<{ ref: React.RefObject<HTMLSpanElement>; className: string | undefined; style: React.CSSProperties | undefined; }, HTMLSpanElement>; /** * Creates a fine-grained reactive attribute that updates independently * Updates DOM attributes directly without component re-renders * * @example * // Direct DOM attribute updates without re-renders * const attrs = useFineGrainedAttr(progress$, 'data-progress'); * // Use {...attrs} to spread onto element */ declare function useFineGrainedAttr<T>(source$: Observable<T>, attributeName: string, transform?: (value: T) => string): Record<string, unknown>; /** * Creates a reactive class name that updates independently * * @example * // Direct CSS class updates without re-renders * const classAttrs = useFineGrainedClass(isActive$, active => active ? 'active' : ''); */ declare function useFineGrainedClass<T>(source$: Observable<T>, transform: (value: T) => string): Record<string, unknown>; /** * Creates a reactive style that updates independently * * @example * // Direct CSS style updates without re-renders * const styleAttrs = useFineGrainedStyle(color$, color => `background-color: ${color}`); */ declare function useFineGrainedStyle<T>(source$: Observable<T>, transform: (value: T) => string): Record<string, unknown>; /** * SolidJS-like Show component with fine-grained reactivity * Only updates visibility without re-rendering children * * @example * // Conditional rendering without parent re-renders * React.createElement(FineGrainedShow, { when: showChild$ }, children); */ declare function FineGrainedShow<T>({ when, fallback, children, }: { when: Observable<T>; fallback?: React.ReactNode; children: React.ReactNode; }): React.FunctionComponentElement<{}> | null; /** * SolidJS-like For component with fine-grained reactivity * Only updates list items that actually changed * * @example * // List rendering with minimal updates * React.createElement(FineGrainedFor, { each: todos$ }, renderItemFunction); */ declare function FineGrainedFor<T>({ each, children, }: { each: Observable<T[]>; children: (item: T, index: number) => React.ReactNode; }): React.FunctionComponentElement<{}>; /** * Wraps a component to use fine-grained reactivity automatically * Makes any component behave like SolidJS components * * @example * // Wraps a component to prevent re-renders * const OptimizedCounter = withFineGrainedReactivity(MyComponent); */ declare function withFineGrainedReactivity<T extends Record<string, unknown>>(Component: React.ComponentType<T>): React.ComponentType<T>; /** * Performance utility: Clean up all reactive subscriptions */ declare function cleanupFineGrainedReactivity(): void; /** * Performance utility: Get fine-grained reactivity statistics */ declare function getFineGrainedStats(): { note: string; memoryManagement: string; performance: string; }; /** * Creates a highly optimized memoized component for signals * - Ultra-fast observable detection with caching * - Optimized prop comparison with early termination * - Component instance caching to avoid recreation * - Pre-compiled constants for maximum speed */ declare function createSignalMemo<T extends Record<string, unknown>>(Component: React.ComponentType<T>): React.MemoExoticComponent<React.ComponentType<T>>; /** * Optimized shallow comparison memo with component caching */ declare function createShallowMemo<T extends Record<string, unknown>>(Component: React.ComponentType<T>): React.MemoExoticComponent<React.ComponentType<T>>; /** * Ultra-optimized signal tracking component with advanced caching * - Component instance caching based on signal signatures * - Fast signal array comparison * - Minimal re-creation overhead * - Optimized ref management */ declare function withSignalTracking<T extends Record<string, unknown>>(Component: React.ComponentType<T>, signals: Observable<unknown>[]): React.ComponentType<T>; /** * Ultra-optimized component that prevents unnecessary re-renders * - Advanced deep equality with caching * - Component instance caching * - Development-only logging with minimal overhead * - Fast path optimizations */ declare function preventUnnecessaryRerenders<T extends Record<string, unknown>>(Component: React.ComponentType<T>): React.MemoExoticComponent<React.ComponentType<T>>; /** * Auto-memoizes any component to prevent unnecessary re-renders * Perfect for child components that don't use signals * * @example * ```tsx * const OptimizedChild = autoMemo(() => { * console.log('This will only render once!'); * return <div>Static content</div>; * }); * ``` */ declare function autoMemo<T extends Record<string, unknown>>(Component: React.ComponentType<T>): React.MemoExoticComponent<React.ComponentType<T>>; /** * Creates a static component that NEVER re-renders after mount * Use for components with no props that display static content * * @example * ```tsx * const StaticChild = staticMemo(() => { * console.log('Renders only once ever!'); * return <div>I never re-render</div>; * }); * ``` */ declare function staticMemo<T extends Record<string, unknown>>(Component: React.ComponentType<T>): React.MemoExoticComponent<React.ComponentType<T>>; /** * Optimizes a component to only re-render when specific props change * * @param Component - The component to memoize * @param propKeys - Array of prop keys that should trigger re-renders * * @example * ```tsx * const OptimizedComponent = propsMemo(MyComponent, ['data', 'isLoading']); * // Only re-renders when data or isLoading props change * ``` */ declare function propsMemo<T extends Record<string, unknown>>(Component: React.ComponentType<T>, propKeys: (keyof T)[]): React.MemoExoticComponent<React.ComponentType<T>>; /** * Hook to automatically memoize child components at runtime * Use this in parent components to prevent child re-renders * * @example * ```tsx * function Parent() { * const count = useSignal(count$, 0); * const MemoizedChild = useMemoizedComponent(Child); * * return ( * <div> * <p>Count: {count}</p> * <MemoizedChild /> * </div> * ); * } * ``` */ declare function useMemoizedComponent<T extends Record<string, unknown>>(Component: React.ComponentType<T>): React.MemoExoticComponent<React.ComponentType<T>>; /** * Performance utility: Clear all internal caches * Useful for memory management in long-running applications * Note: WeakMaps don't have clear() method, they auto-cleanup */ declare function clearMemoizationCaches(): void; /** * Performance utility: Get cache statistics * Note: WeakMaps don't expose size information */ declare function getMemoizationStats(): { componentMemoCache: string; signalTrackingCache: string; note: string; }; /** * Creates a fine-grained selector that only updates when the selected value changes * This helps reduce re-renders by only subscribing to specific parts of state */ declare function createSelector<T, R>(source$: Observable<T>, selector: (value: T) => R, equalityFn?: (a: R, b: R) => boolean): Observable<R>; /** * Creates multiple selectors at once for better performance */ declare function createSelectors<T, R extends Record<string, unknown>>(source$: Observable<T>, selectors: { [K in keyof R]: (value: T) => R[K]; }, equalityFn?: (a: R[keyof R], b: R[keyof R]) => boolean): { [K in keyof R]: Observable<R[K]>; }; /** * Shallow equality function for objects */ declare function shallowEqual<T>(a: T, b: T): boolean; /** * Creates a derived value that depends on multiple signals * Only updates when any of the dependencies change */ declare function createDerived<T, R>(source$: Observable<T>, deriveFn: (value: T) => R, equalityFn?: (a: R, b: R) => boolean): Observable<R>; /** * Creates a selector with memoization for expensive computations */ declare function createMemoizedSelector<T, R>(source$: Observable<T>, selector: (value: T) => R, equalityFn?: (a: R, b: R) => boolean): Observable<R>; export { FineGrainedFor, FineGrainedShow, FineGrainedText, SignalConditions, StoreConditions, autoMemo, batchSignalUpdates, batchStoreUpdates, cleanupFineGrainedReactivity, clearMemoizationCaches, clearSubscriptionPool, compareStoreStates, createComputed, createDeepSelector, createDerived, createMemoizedSelector, createSelector, createSelectors, createShallowMemo, createSignal, createSignalMemo, createSignalWithMetrics, createSignals, createStore, createStoreWithMetrics, createStores, getFineGrainedStats, getMemoizationStats, getSubscriptionPoolStats, preventUnnecessaryRerenders, propsMemo, shallowEqual, staticMemo, useAutoMemo, useDebouncedSignalEffect, useFineGrainedAttr, useFineGrainedClass, useFineGrainedStyle, useMemoizedComponent, useSignal, useSignalCallback, useSignalEffect, useSignalLifecycle, useSignalTextProps, useSignalValue, useStaticMemo, useStore, useTrueFineGrainedValue, withAutoMemo, withFineGrainedReactivity, withSignalTracking };