All files / react/src lifecycle.ts

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

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 1121x     1x 1x                                     1x 14x 14x   14x 14x 10x 3x   3x 2x 2x 10x 10x 14x 21x 8x 21x   21x 21x 21x 14x 13x 13x   13x 13x   13x 13x 13x 13x 13x 13x 13x 14x 14x                       1x 4x   4x 1x 1x 1x 1x 1x 1x 1x 1x 1x   4x 4x                       1x 19x   19x 6x 15x 13x 13x 19x     1x  
import { captureStack, closure, onGlobalCleanup, setCleanUpHandler, untrack } from '@anchorlib/core';
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(): Lifecycle {
  const mountHandlers = new Set<MountHandler>();
  const cleanupHandlers = new Set<CleanupHandler>();
 
  return {
    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);
 
      closure.set(MOUNT_HANDLER_SYMBOL, mountHandlers);
      closure.set(CLEANUP_HANDLER_SYMBOL, cleanupHandlers);
 
      try {
        return untrack(fn) as R;
      } finally {
        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);