All files / packages/hooks/src useRunEffect.ts

96.66% Statements 29/30
87.5% Branches 7/8
100% Functions 7/7
96.66% Lines 29/30

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                                          28x 28x 28x 28x   28x 28x       28x   28x 16x 16x     28x 11x 11x     11x 1x     11x     5x 5x 5x     6x 6x 6x         11x     28x 26x       26x   26x 20x         28x    
import { useCallback, useEffect, useRef, useState } from "react";
 
import { Effect, Fiber } from "effect";
 
import { type DomainError } from "@repo/effect-utils";
 
type RunEffectResult<A, E> = {
  data: A | null;
  error: E | null;
  loading: boolean;
  run: () => void;
};
 
export function useRunEffect<A, E = DomainError>(
  effect: Effect.Effect<A, E>,
  options?: {
    immediate?: boolean;
    onSuccess?: (data: A) => void;
    onError?: (error: E) => void;
  },
): RunEffectResult<A, E> {
  const [data, setData] = useState<A | null>(null);
  const [error, setError] = useState<E | null>(null);
  const [loading, setLoading] = useState(false);
  const immediate = options?.immediate ?? false;
 
  const onSuccessRef = useRef(options?.onSuccess);
  const onErrorRef = useRef(options?.onError);
 
  // The fiber runs the match effect, which returns void.
  // The error type is never because match handles both success and failure.
  const fiberRef = useRef<Fiber.RuntimeFiber<void, never> | null>(null);
 
  useEffect(() => {
    onSuccessRef.current = options?.onSuccess;
    onErrorRef.current = options?.onError;
  }, [options?.onError, options?.onSuccess]);
 
  const run = useCallback(() => {
    setLoading(true);
    setError(null);
 
    // Cancel previous execution if running
    if (fiberRef.current) {
      Effect.runFork(Fiber.interrupt(fiberRef.current));
    }
 
    const fiber = Effect.runFork(
      Effect.match(effect, {
        onFailure: (e) => {
          setError(e);
          setLoading(false);
          onErrorRef.current?.(e);
        },
        onSuccess: (a) => {
          setData(a);
          setLoading(false);
          onSuccessRef.current?.(a);
        },
      }),
    );
 
    fiberRef.current = fiber;
  }, [effect]);
 
  useEffect(() => {
    Iif (immediate) {
      run();
    }
 
    return () => {
      // Cleanup on unmount
      if (fiberRef.current) {
        Effect.runFork(Fiber.interrupt(fiberRef.current));
      }
    };
  }, [immediate, run]);
 
  return { data, error, loading, run };
}