import { Effect, ExecutionStrategy, Runtime, Scope } from "effect" import * as React from "react" export interface ScopeOptions { readonly finalizerExecutionStrategy?: ExecutionStrategy.ExecutionStrategy readonly finalizerExecutionMode?: "sync" | "fork" } export const useMemo: { ( factory: () => Effect.Effect, deps: React.DependencyList, ): Effect.Effect } = Effect.fnUntraced(function* useMemo( factory: () => Effect.Effect, deps: React.DependencyList, ) { const runtime = yield* Effect.runtime() return React.useMemo(() => Runtime.runSync(runtime)(factory()), deps) }) export const useOnce: { (factory: () => Effect.Effect): Effect.Effect } = Effect.fnUntraced(function* useOnce( factory: () => Effect.Effect ) { return yield* useMemo(factory, []) }) export const useEffect: { ( effect: () => Effect.Effect, deps?: React.DependencyList, options?: ScopeOptions, ): Effect.Effect } = Effect.fnUntraced(function* useEffect( effect: () => Effect.Effect, deps?: React.DependencyList, options?: ScopeOptions, ) { const runtime = yield* Effect.runtime() React.useEffect(() => { const { scope, exit } = Effect.Do.pipe( Effect.bind("scope", () => Scope.make(options?.finalizerExecutionStrategy ?? ExecutionStrategy.sequential)), Effect.bind("exit", ({ scope }) => Effect.exit(Effect.provideService(effect(), Scope.Scope, scope))), Runtime.runSync(runtime), ) return () => { switch (options?.finalizerExecutionMode ?? "sync") { case "sync": Runtime.runSync(runtime)(Scope.close(scope, exit)) break case "fork": Runtime.runFork(runtime)(Scope.close(scope, exit)) break } } }, deps) }) export const useLayoutEffect: { ( effect: () => Effect.Effect, deps?: React.DependencyList, options?: ScopeOptions, ): Effect.Effect } = Effect.fnUntraced(function* useLayoutEffect( effect: () => Effect.Effect, deps?: React.DependencyList, options?: ScopeOptions, ) { const runtime = yield* Effect.runtime() React.useLayoutEffect(() => { const { scope, exit } = Effect.Do.pipe( Effect.bind("scope", () => Scope.make(options?.finalizerExecutionStrategy ?? ExecutionStrategy.sequential)), Effect.bind("exit", ({ scope }) => Effect.exit(Effect.provideService(effect(), Scope.Scope, scope))), Runtime.runSync(runtime), ) return () => { switch (options?.finalizerExecutionMode ?? "sync") { case "sync": Runtime.runSync(runtime)(Scope.close(scope, exit)) break case "fork": Runtime.runFork(runtime)(Scope.close(scope, exit)) break } } }, deps) })