All files / react/src lifecycle.ts

100% Statements 74/74
100% Branches 14/14
100% Functions 6/6
100% Lines 74/74

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 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 1401x                       1x     1x 1x                                     1x 26x 26x   26x 26x   26x 26x   26x 26x 26x 26x 26x 26x 22x 3x   3x 2x 2x 22x 22x 26x 45x 110x 45x   45x 45x 45x 26x 25x 25x 25x 25x   25x 25x 25x 25x   25x 25x 25x 25x 25x 25x 25x 25x 25x 26x 26x                       1x 4x   4x 1x 1x 1x 1x 1x 1x 1x 1x 1x   4x 4x                       1x 181x   181x 108x 177x 73x 73x 181x     1x  
import {
  anchor,
  captureStack,
  closure,
  createRenderCtx,
  createStack,
  onGlobalCleanup,
  type RefStack,
  setCleanUpHandler,
  STACK_SYMBOL,
  untrack,
} from '@anchorlib/core';
import { PROPS_SYMBOL, proxyProps } from './props.js';
import type { CleanupHandler, Lifecycle, MountHandler } from './types.js';
 
const MOUNT_HANDLER_SYMBOL = Symbol('mount-handler');
const CLEANUP_HANDLER_SYMBOL = Symbol('mount-cleanup');
 
/**
 * Creates a new lifecycle manager for handling component mount, cleanup, and rendering operations.
 *
 * The lifecycle manager provides three core methods:
 * - `mount()`: Schedules and executes mount handlers and effects
 * - `cleanup()`: Schedules and executes cleanup handlers and clears all handlers
 * - `render()`: Executes a render function within the component's context
 *
 * This function manages:
 * - Mount handlers registered via `onMount()`
 * - Cleanup handlers registered via `onCleanup()`
 * - Effects registered via `effect()`
 *
 * It also handles the proper execution order and cleanup of effects with their cleanup functions.
 *
 * @returns A Lifecycle object with mount, cleanup, and render methods
 */
export function createLifecycle(setupProps: Record<string, unknown>, name?: string): Lifecycle {
  const mountHandlers = new Set<MountHandler>();
  const cleanupHandlers = new Set<CleanupHandler>();
 
  const context = createRenderCtx(name);
  const propsRef = anchor({ ...setupProps }, { recursive: false });
 
  const stack = createStack();
  const props = proxyProps(propsRef);
 
  return {
    stack,
    props,
    propsRef: propsRef,
    context,
    mount() {
      mountHandlers.forEach((mount) => {
        const cleanup = mount();
 
        if (typeof cleanup === 'function') {
          cleanupHandlers.add(cleanup);
        }
      });
    },
    cleanup() {
      cleanupHandlers.forEach((cleanup) => {
        cleanup();
      });
 
      mountHandlers.clear();
      cleanupHandlers.clear();
    },
    render<R>(fn: () => R) {
      const prevMountHandlers = closure.get<Set<MountHandler>>(MOUNT_HANDLER_SYMBOL),
        prevCleanupHandlers = closure.get<Set<CleanupHandler>>(CLEANUP_HANDLER_SYMBOL),
        prevStack = closure.get<RefStack>(STACK_SYMBOL),
        prevProps = closure.get<Record<string, unknown>>(PROPS_SYMBOL);
 
      closure.set(STACK_SYMBOL, stack);
      closure.set(PROPS_SYMBOL, props);
      closure.set(MOUNT_HANDLER_SYMBOL, mountHandlers);
      closure.set(CLEANUP_HANDLER_SYMBOL, cleanupHandlers);
 
      try {
        return untrack(fn) as R;
      } finally {
        closure.set(STACK_SYMBOL, prevStack);
        closure.set(PROPS_SYMBOL, prevProps);
        closure.set(MOUNT_HANDLER_SYMBOL, prevMountHandlers);
        closure.set(CLEANUP_HANDLER_SYMBOL, prevCleanupHandlers);
      }
    },
  };
}
 
/**
 * Registers a mount handler function that will be executed when the component is mounted.
 *
 * Mount handlers are executed when the component is being set up and can optionally
 * return a cleanup function that will be called when the component is unmounted.
 *
 * @param fn - The mount handler function to register
 *
 * @throws {Error} If called outside a Setup component context
 */
export function onMount(fn: MountHandler) {
  const currentMountHandlers = closure.get<Set<MountHandler>>(MOUNT_HANDLER_SYMBOL);
 
  if (!currentMountHandlers) {
    const error = new Error('Out of Setup component.');
    captureStack.violation.general(
      'Mount handler declaration violation detected:',
      'Attempted to use mount handler outside of Setup component.',
      error,
      undefined,
      onMount
    );
  }
 
  currentMountHandlers?.add(fn);
}
 
/**
 * Registers a cleanup handler function that will be executed when the component is cleaned up.
 *
 * Cleanup handlers are executed when the component is being torn down, typically to
 * clean up resources like event listeners, timers, or subscriptions.
 *
 * @param fn - The cleanup handler function to register
 *
 * @throws {Error} If called outside a Setup component context
 */
export function onCleanup(fn: CleanupHandler) {
  const currentCleanupHandlers = closure.get<Set<CleanupHandler>>(CLEANUP_HANDLER_SYMBOL);
 
  if (currentCleanupHandlers) {
    currentCleanupHandlers?.add(fn);
  } else {
    return onGlobalCleanup(fn);
  }
}
 
// Hook up cleanup handler to the Anchor's core lifecycle.
setCleanUpHandler(onCleanup);