All files / src/primitives effect.ts

100% Statements 33/33
100% Branches 12/12
100% Functions 3/3
100% Lines 33/33

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 841x     1x 1x 1x                                                                   1x   185x 185x 185x 185x   185x 185x   355x   355x   355x 355x   355x 355x 355x   355x   355x 355x 355x 355x 185x   185x 185x     185x   185x 144x 144x     185x   185x 185x  
import { effectStack, cleanup } from '../internal/scheduler';
import type { Subscriber } from '../internal/scheduler';
import type { Disposer, ReactiveOptions } from '../types';
import { hasOwner, onCleanup } from '../lifecycle/lifecycle';
import { isProfilerEnabled, recordEffectExecution } from '../internal/profiler';
import { devWarning } from '../error';
 
/**
 * Creates an effect that automatically re-runs when its dependencies change.
 *
 * Effects are functions that run immediately and re-run whenever any signals they
 * read during execution change. They're the primary way to create side effects
 * that respond to reactive state changes.
 *
 * @param fn The function to run as an effect - will be called immediately and whenever dependencies change
 * @param options Optional configuration including debug name
 * @returns A disposer function to manually stop the effect and clean up resources
 *
 * @example
 * ```typescript
 * const [count, setCount] = createSignal(0);
 *
 * createEffect(() => {
 *   console.log('Count changed to:', count());
 *   document.title = `Count: ${count()}`;
 * });
 *
 * setCount(1); // Logs: "Count changed to: 1"
 * setCount(2); // Logs: "Count changed to: 2"
 *
 * // Effects must be created within a reactive root for proper cleanup
 * createRoot(() => {
 *   const dispose = createEffect(() => {
 *     // ... effect logic
 *   });
 *   // dispose() will clean up the effect when called
 * });
 * ```
 */
export function createEffect(fn: () => void, options?: ReactiveOptions): Disposer {
  // Add the warning here
  devWarning(
    hasOwner(),
    `createEffect(${options?.name ? `"${options.name}"` : ''}) was called outside of a reactive root. This effect will not be automatically cleaned up.`
  );
 
  const effect: Subscriber = {
    execute: () => {
      // Clean up any old dependencies before re-running the effect.
      cleanup(effect);
      // Push this effect onto the global stack to track new dependencies.
      effectStack.push(effect);
 
      const profiling = isProfilerEnabled();
      const start = profiling ? performance.now() : 0;
 
      try {
        fn();
      } finally {
        // Always pop the effect from the stack after execution.
        effectStack.pop();
 
        const duration = profiling ? (performance.now() - start) : 0;
        recordEffectExecution(effect.name || 'anonymous', duration);
      }
    },
    dependencies: new Set(),
    // NEW: Store the name on the subscriber object
    name: options?.name,
  };
 
  // Run the effect immediately to establish its initial dependencies.
  effect.execute();
 
  const disposer = () => {
    cleanup(effect);
  };
 
  // NEW: Register the effect's own disposer with the current owner.
  onCleanup(disposer);
 
  return disposer;
}