diff --git a/packages/effect-components/src/ReactComponent.ts b/packages/effect-components/src/ReactComponent.ts index af24644..06c33e5 100644 --- a/packages/effect-components/src/ReactComponent.ts +++ b/packages/effect-components/src/ReactComponent.ts @@ -1,24 +1,30 @@ -import { Effect, Runtime, type Scope } from "effect" +import { Effect, ExecutionStrategy, Exit, Ref, Runtime, Scope } from "effect" import * as React from "react" +import * as ReactHook from "./ReactHook.js" export interface ReactComponent { (props: P): Effect.Effect } + export const use = ( self: ReactComponent, fn: (Component: React.FC

) => React.ReactNode, -): Effect.Effect => Effect.map( + options?: ReactHook.ScopeOptions, +): Effect.Effect> => Effect.map( Effect.runtime(), - runtime => fn(props => Runtime.runSync(runtime)(self(props))), + runtime => fn(props => + Runtime.runSync(runtime)(Effect.provideService(self(props), Scope.Scope, useScope(runtime, options))) + ), ) export const useFC = ( - self: ReactComponent + self: ReactComponent, + options?: ReactHook.ScopeOptions, ): Effect.Effect, never, R | Scope.Scope> => Effect.map( Effect.runtime(), - runtime => props => Runtime.runSync(runtime)(self(props)), + runtime => props => Runtime.runSync(runtime)(Effect.provideService(self(props), Scope.Scope, useScope(runtime, options))), ) export const createElement = ( @@ -34,6 +40,45 @@ export const createElement = ( ), ) -export const useScope: Effect.Effect = Effect.gen(function*() { -}) +const useScope = ( + runtime: Runtime.Runtime, + options?: ReactHook.ScopeOptions, +): Scope.Scope => { + const [isInitialRun, initialScope] = React.useMemo(() => Runtime.runSync(runtime)( + Effect.all([Ref.make(true), makeScope(options)]) + ), []) + const [scope, setScope] = React.useState(initialScope) + + React.useEffect(() => Runtime.runSync(runtime)( + Effect.if(isInitialRun, { + onTrue: () => Effect.as( + Ref.set(isInitialRun, false), + () => closeScope(scope, runtime, options), + ), + + onFalse: () => makeScope(options).pipe( + Effect.tap(scope => Effect.sync(() => setScope(scope))), + Effect.map(scope => () => closeScope(scope, runtime, options)), + ), + }) + ), []) + + return scope +} + +const makeScope = (options?: ReactHook.ScopeOptions) => Scope.make(options?.finalizerExecutionStrategy ?? ExecutionStrategy.sequential) +const closeScope = ( + scope: Scope.CloseableScope, + runtime: Runtime.Runtime, + options?: ReactHook.ScopeOptions, +) => { + switch (options?.finalizerExecutionMode ?? "sync") { + case "sync": + Runtime.runSync(runtime)(Scope.close(scope, Exit.void)) + break + case "fork": + Runtime.runFork(runtime)(Scope.close(scope, Exit.void)) + break + } +} diff --git a/packages/effect-components/src/hooks.ts b/packages/effect-components/src/ReactHook.ts similarity index 100% rename from packages/effect-components/src/hooks.ts rename to packages/effect-components/src/ReactHook.ts diff --git a/packages/effect-components/src/index.ts b/packages/effect-components/src/index.ts index 153be4b..3db5a6c 100644 --- a/packages/effect-components/src/index.ts +++ b/packages/effect-components/src/index.ts @@ -1,3 +1,2 @@ -export * from "./hooks.js" export * as ReactComponent from "./ReactComponent.js" -export { use, useFC, createElement } from "./ReactComponent.js" +export * as ReactHook from "./ReactHook.js"