UNPKG

fluidstate-preact

Version:

Library for using fine-grained reactivity state management library fluidstate in Preact

128 lines (115 loc) 5.63 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.useSignals = void 0; var _hooks = require("preact/hooks"); var _onceRef = require("./once-ref"); var _signals = require("@preact/signals"); var _fluidstate = require("fluidstate"); /** * A Preact hook that bridges `fluidstate` reactivity with `@preact/signals`. * It returns a `get` function that can be used to create memoized Preact signals * from `fluidstate` reactive state. This is the most performant way to use * `fluidstate` in Preact, as it avoids component re-renders and updates only * the specific parts of the DOM that depend on the state. * * @param Context - Optional. A Preact Context object. If provided, the state * from this context will be available to the selector functions passed to `get`. * This is typically used with `createReactiveSetup`. * @returns A `get` function that takes a selector and returns a Preact Signal. */ const useSignals = Context => { // A ref to store the context object and its latest value. // This helps in detecting when the context value itself changes. const context = (0, _onceRef.useOnceRef)(() => ({ Context, contextValue: null })); // Get the current context value. If a context was provided, use `useContext`. const contextValue = context.current.Context ? (0, _hooks.useContext)(context.current.Context) : null; // Check if the context value has changed since the last render. const isContextValueChanged = contextValue !== context.current.contextValue; context.current.contextValue = contextValue; // This ref holds all the reactive primitives for each `get` call. // We use arrays and rely on the call index, similar to how React hooks work. const data = (0, _onceRef.useOnceRef)(() => ({ latestIndex: 0, fns: [], fnAtoms: [], resultSignals: [], computeds: [], reactions: [] })); // Reset the index at the beginning of each render. The `get` function will increment it for each call. data.current.latestIndex = -1; // This effect runs after every render to clean up any reactive primitives // that are no longer used (e.g., due to conditional rendering). (0, _hooks.useEffect)(() => { const fnsLength = data.current.latestIndex + 1; // If the number of `get` calls decreased, stop the reactions for the orphaned signals. for (let i = fnsLength; i < data.current.reactions.length; i++) { data.current.reactions[i].stop(); } // Truncate the arrays to the new length to release memory. data.current.fns.length = fnsLength; data.current.fnAtoms.length = fnsLength; data.current.resultSignals.length = fnsLength; data.current.reactions.length = fnsLength; }); // This effect runs only once on mount and cleans up all reactions when the component unmounts. (0, _hooks.useEffect)(() => { return () => { for (let i = 0; i < data.current.reactions.length; i++) { data.current.reactions[i].stop(); } }; }, []); /** * Creates and returns a memoized Preact signal for a given piece of `fluidstate` data. * @param fn A selector function that accesses `fluidstate` reactive state and returns a value. * @returns A Preact `Signal` that will automatically update when the underlying state changes. */ return fn => { const currentIndex = ++data.current.latestIndex; // Check if we've already set up the reactive primitives for this index. if (data.current.fns[currentIndex]) { // If so, this is a subsequent render. Check if the selector function or context value has changed. if (data.current.fns[currentIndex] !== fn || isContextValueChanged) { // If they have changed, update the function and notify `fluidstate` that this dependency has changed. // This will trigger the computed atom to re-evaluate. data.current.fns[currentIndex] = fn; data.current.fnAtoms[currentIndex].reportChanged(); } } else { // This is the first time this `get` call is being made (at this index). // We need to set up the entire reactive chain. data.current.fns[currentIndex] = fn; data.current.fnAtoms[currentIndex] = (0, _fluidstate.createAtom)(`useSignals/fnAtom@${currentIndex}`); data.current.resultSignals[currentIndex] = (0, _signals.signal)(null); // The core of the bridge: a `fluidstate` computed that, when run, // executes the selector `fn` and updates the Preact signal's value. // It depends on the `fnAtom` (for selector changes) and any reactive // state accessed inside `fn`. data.current.computeds[currentIndex] = (0, _fluidstate.createComputedAtom)(`useSignals/computed@${currentIndex}`, () => { data.current.fnAtoms[currentIndex].reportObserved(); let result = data.current.fns[currentIndex](context.current.contextValue); if (Array.isArray(result)) { result.length; result = Object.create(result); } data.current.resultSignals[currentIndex].value = result; }); // A reaction is needed to keep the computed atom "hot" or "active". // This ensures that the computed re-evaluates automatically whenever // its dependencies change, thus keeping the Preact signal in sync. data.current.reactions[currentIndex] = (0, _fluidstate.createReaction)(() => { data.current.computeds[currentIndex].get(); }); } // Return the memoized Preact signal for this index. return data.current.resultSignals[currentIndex]; }; }; exports.useSignals = useSignals; //# sourceMappingURL=signals.js.map