/** biome-ignore-all lint/complexity/noBannedTypes: {} is the default type for React props */ /** biome-ignore-all lint/complexity/useArrowFunction: necessary for class prototypes */ import { Context, type Duration, Effect, Equivalence, ExecutionStrategy, Exit, Fiber, Function, HashMap, identity, Layer, ManagedRuntime, Option, Pipeable, Predicate, Ref, Runtime, Scope, Tracer, type Utils } from "effect" import * as React from "react" export const ComponentTypeId: unique symbol = Symbol.for("@effect-fc/Component/Component") export type ComponentTypeId = typeof ComponentTypeId /** * Represents an Effect-based React Component that integrates the Effect system with React. */ export interface Component

extends ComponentPrototype, ComponentOptions { new(_: never): Record readonly [ComponentTypeId]: ComponentTypeId readonly "~Props": P readonly "~Success": A readonly "~Error": E readonly "~Context": R readonly body: (props: P) => Effect.Effect } export declare namespace Component { export type Props> = [T] extends [Component] ? P : never export type Success> = [T] extends [Component] ? A : never export type Error> = [T] extends [Component] ? E : never export type Context> = [T] extends [Component] ? R : never export type AsComponent> = Component, Success, Error, Context> } export interface ComponentPrototype

extends Pipeable.Pipeable { readonly [ComponentTypeId]: ComponentTypeId readonly use: Effect.Effect<(props: P) => A, never, Exclude> asFunctionComponent( runtimeRef: React.Ref>> ): (props: P) => A setFunctionComponentName(f: React.FC

): void transformFunctionComponent(f: React.FC

): React.FC

} export const ComponentPrototype: ComponentPrototype = Object.freeze({ [ComponentTypeId]: ComponentTypeId, ...Pipeable.Prototype, get use() { return use(this) }, asFunctionComponent

( this: Component, runtimeRef: React.RefObject>>, ) { return (props: P) => Runtime.runSync(runtimeRef.current)( Effect.andThen( useScope([], this), scope => Effect.provideService(this.body(props), Scope.Scope, scope), ) ) }, setFunctionComponentName

( this: Component, f: React.FC

, ) { f.displayName = this.displayName ?? "Anonymous" }, transformFunctionComponent: identity, } as const) const use = Effect.fnUntraced(function*

( self: Component ) { // biome-ignore lint/style/noNonNullAssertion: React ref initialization const runtimeRef = React.useRef>>(null!) runtimeRef.current = yield* Effect.runtime>() return yield* React.useState(() => Runtime.runSync(runtimeRef.current)(Effect.cachedFunction( (_services: readonly any[]) => Effect.sync(() => { const f: React.FC

= self.asFunctionComponent(runtimeRef) self.setFunctionComponentName(f) return self.transformFunctionComponent(f) }), Equivalence.array(Equivalence.strict()), )))[0](Array.from( Context.omit(...self.nonReactiveTags)(runtimeRef.current.context).unsafeMap.values() )) }) export interface ComponentOptions { /** * Custom display name for the component in React DevTools and debugging utilities. */ readonly displayName?: string /** * Context tags that should not trigger component remount when their values change. * * @default [Tracer.ParentSpan] */ readonly nonReactiveTags: readonly Context.Tag[] /** * Specifies the execution strategy for finalizers when the component unmounts or its scope closes. * Determines whether finalizers execute sequentially or in parallel. * * @default ExecutionStrategy.sequential */ readonly finalizerExecutionStrategy: ExecutionStrategy.ExecutionStrategy /** * Debounce duration before executing finalizers after component unmount. * Prevents unnecessary cleanup work during rapid remount/unmount cycles, * which is common in development and certain UI patterns. * * @default "100 millis" */ readonly finalizerExecutionDebounce: Duration.DurationInput } export const defaultOptions: ComponentOptions = { nonReactiveTags: [Tracer.ParentSpan], finalizerExecutionStrategy: ExecutionStrategy.sequential, finalizerExecutionDebounce: "100 millis", } export const isComponent = (u: unknown): u is Component<{}, React.ReactNode, unknown, unknown> => Predicate.hasProperty(u, ComponentTypeId) export declare namespace make { export type Gen = { >, A extends React.ReactNode, P extends {} = {}>( body: (props: P) => Generator ): Component< P, A, [Eff] extends [never] ? never : [Eff] extends [Utils.YieldWrap>] ? E : never, [Eff] extends [never] ? never : [Eff] extends [Utils.YieldWrap>] ? R : never > >, A, B extends Effect.Effect, P extends {} = {}>( body: (props: P) => Generator, a: ( _: Effect.Effect< A, [Eff] extends [never] ? never : [Eff] extends [Utils.YieldWrap>] ? E : never, [Eff] extends [never] ? never : [Eff] extends [Utils.YieldWrap>] ? R : never >, props: NoInfer

, ) => B, ): Component>, Effect.Effect.Error, Effect.Effect.Context> >, A, B, C extends Effect.Effect, P extends {} = {}>( body: (props: P) => Generator, a: ( _: Effect.Effect< A, [Eff] extends [never] ? never : [Eff] extends [Utils.YieldWrap>] ? E : never, [Eff] extends [never] ? never : [Eff] extends [Utils.YieldWrap>] ? R : never >, props: NoInfer

, ) => B, b: (_: B, props: NoInfer

) => C, ): Component>, Effect.Effect.Error, Effect.Effect.Context> >, A, B, C, D extends Effect.Effect, P extends {} = {}>( body: (props: P) => Generator, a: ( _: Effect.Effect< A, [Eff] extends [never] ? never : [Eff] extends [Utils.YieldWrap>] ? E : never, [Eff] extends [never] ? never : [Eff] extends [Utils.YieldWrap>] ? R : never >, props: NoInfer

, ) => B, b: (_: B, props: NoInfer

) => C, c: (_: C, props: NoInfer

) => D, ): Component>, Effect.Effect.Error, Effect.Effect.Context> >, A, B, C, D, E extends Effect.Effect, P extends {} = {}>( body: (props: P) => Generator, a: ( _: Effect.Effect< A, [Eff] extends [never] ? never : [Eff] extends [Utils.YieldWrap>] ? E : never, [Eff] extends [never] ? never : [Eff] extends [Utils.YieldWrap>] ? R : never >, props: NoInfer

, ) => B, b: (_: B, props: NoInfer

) => C, c: (_: C, props: NoInfer

) => D, d: (_: D, props: NoInfer

) => E, ): Component>, Effect.Effect.Error, Effect.Effect.Context> >, A, B, C, D, E, F extends Effect.Effect, P extends {} = {}>( body: (props: P) => Generator, a: ( _: Effect.Effect< A, [Eff] extends [never] ? never : [Eff] extends [Utils.YieldWrap>] ? E : never, [Eff] extends [never] ? never : [Eff] extends [Utils.YieldWrap>] ? R : never >, props: NoInfer

, ) => B, b: (_: B, props: NoInfer

) => C, c: (_: C, props: NoInfer

) => D, d: (_: D, props: NoInfer

) => E, e: (_: E, props: NoInfer

) => F, ): Component>, Effect.Effect.Error, Effect.Effect.Context> >, A, B, C, D, E, F, G extends Effect.Effect, P extends {} = {}>( body: (props: P) => Generator, a: ( _: Effect.Effect< A, [Eff] extends [never] ? never : [Eff] extends [Utils.YieldWrap>] ? E : never, [Eff] extends [never] ? never : [Eff] extends [Utils.YieldWrap>] ? R : never >, props: NoInfer

, ) => B, b: (_: B, props: NoInfer

) => C, c: (_: C, props: NoInfer

) => D, d: (_: D, props: NoInfer

) => E, e: (_: E, props: NoInfer

) => F, f: (_: F, props: NoInfer

) => G, ): Component>, Effect.Effect.Error, Effect.Effect.Context> >, A, B, C, D, E, F, G, H extends Effect.Effect, P extends {} = {}>( body: (props: P) => Generator, a: ( _: Effect.Effect< A, [Eff] extends [never] ? never : [Eff] extends [Utils.YieldWrap>] ? E : never, [Eff] extends [never] ? never : [Eff] extends [Utils.YieldWrap>] ? R : never >, props: NoInfer

, ) => B, b: (_: B, props: NoInfer

) => C, c: (_: C, props: NoInfer

) => D, d: (_: D, props: NoInfer

) => E, e: (_: E, props: NoInfer

) => F, f: (_: F, props: NoInfer

) => G, g: (_: G, props: NoInfer

) => H, ): Component>, Effect.Effect.Error, Effect.Effect.Context> >, A, B, C, D, E, F, G, H, I extends Effect.Effect, P extends {} = {}>( body: (props: P) => Generator, a: ( _: Effect.Effect< A, [Eff] extends [never] ? never : [Eff] extends [Utils.YieldWrap>] ? E : never, [Eff] extends [never] ? never : [Eff] extends [Utils.YieldWrap>] ? R : never >, props: NoInfer

, ) => B, b: (_: B, props: NoInfer

) => C, c: (_: C, props: NoInfer

) => D, d: (_: D, props: NoInfer

) => E, e: (_: E, props: NoInfer

) => F, f: (_: F, props: NoInfer

) => G, g: (_: G, props: NoInfer

) => H, h: (_: H, props: NoInfer

) => I, ): Component>, Effect.Effect.Error, Effect.Effect.Context> >, A, B, C, D, E, F, G, H, I, J extends Effect.Effect, P extends {} = {}>( body: (props: P) => Generator, a: ( _: Effect.Effect< A, [Eff] extends [never] ? never : [Eff] extends [Utils.YieldWrap>] ? E : never, [Eff] extends [never] ? never : [Eff] extends [Utils.YieldWrap>] ? R : never >, props: NoInfer

, ) => B, b: (_: B, props: NoInfer

) => C, c: (_: C, props: NoInfer

) => D, d: (_: D, props: NoInfer

) => E, e: (_: E, props: NoInfer

) => F, f: (_: F, props: NoInfer

) => G, g: (_: G, props: NoInfer

) => H, h: (_: H, props: NoInfer

) => I, i: (_: I, props: NoInfer

) => J, ): Component>, Effect.Effect.Error, Effect.Effect.Context> } export type NonGen = { , P extends {} = {}>( body: (props: P) => Eff ): Component>, Effect.Effect.Error, Effect.Effect.Context> , A, P extends {} = {}>( body: (props: P) => A, a: (_: A, props: NoInfer

) => Eff, ): Component>, Effect.Effect.Error, Effect.Effect.Context> , A, B, P extends {} = {}>( body: (props: P) => A, a: (_: A, props: NoInfer

) => B, b: (_: B, props: NoInfer

) => Eff, ): Component>, Effect.Effect.Error, Effect.Effect.Context> , A, B, C, P extends {} = {}>( body: (props: P) => A, a: (_: A, props: NoInfer

) => B, b: (_: B, props: NoInfer

) => C, c: (_: C, props: NoInfer

) => Eff, ): Component>, Effect.Effect.Error, Effect.Effect.Context> , A, B, C, D, P extends {} = {}>( body: (props: P) => A, a: (_: A, props: NoInfer

) => B, b: (_: B, props: NoInfer

) => C, c: (_: C, props: NoInfer

) => D, d: (_: D, props: NoInfer

) => Eff, ): Component>, Effect.Effect.Error, Effect.Effect.Context> , A, B, C, D, E, P extends {} = {}>( body: (props: P) => A, a: (_: A, props: NoInfer

) => B, b: (_: B, props: NoInfer

) => C, c: (_: C, props: NoInfer

) => D, d: (_: D, props: NoInfer

) => E, e: (_: E, props: NoInfer

) => Eff, ): Component>, Effect.Effect.Error, Effect.Effect.Context> , A, B, C, D, E, F, P extends {} = {}>( body: (props: P) => A, a: (_: A, props: NoInfer

) => B, b: (_: B, props: NoInfer

) => C, c: (_: C, props: NoInfer

) => D, d: (_: D, props: NoInfer

) => E, e: (_: E, props: NoInfer

) => F, f: (_: F, props: NoInfer

) => Eff, ): Component>, Effect.Effect.Error, Effect.Effect.Context> , A, B, C, D, E, F, G, P extends {} = {}>( body: (props: P) => A, a: (_: A, props: NoInfer

) => B, b: (_: B, props: NoInfer

) => C, c: (_: C, props: NoInfer

) => D, d: (_: D, props: NoInfer

) => E, e: (_: E, props: NoInfer

) => F, f: (_: F, props: NoInfer

) => G, g: (_: G, props: NoInfer

) => Eff, ): Component>, Effect.Effect.Error, Effect.Effect.Context> , A, B, C, D, E, F, G, H, P extends {} = {}>( body: (props: P) => A, a: (_: A, props: NoInfer

) => B, b: (_: B, props: NoInfer

) => C, c: (_: C, props: NoInfer

) => D, d: (_: D, props: NoInfer

) => E, e: (_: E, props: NoInfer

) => F, f: (_: F, props: NoInfer

) => G, g: (_: G, props: NoInfer

) => H, h: (_: H, props: NoInfer

) => Eff, ): Component>, Effect.Effect.Error, Effect.Effect.Context> , A, B, C, D, E, F, G, H, I, P extends {} = {}>( body: (props: P) => A, a: (_: A, props: NoInfer

) => B, b: (_: B, props: NoInfer

) => C, c: (_: C, props: NoInfer

) => D, d: (_: D, props: NoInfer

) => E, e: (_: E, props: NoInfer

) => F, f: (_: F, props: NoInfer

) => G, g: (_: G, props: NoInfer

) => H, h: (_: H, props: NoInfer

) => I, i: (_: I, props: NoInfer

) => Eff, ): Component>, Effect.Effect.Error, Effect.Effect.Context> } } /** * Creates an Effect-FC Component using the same overloads and pipeline composition style as `Effect.fn`. * * This is the **recommended** approach for defining Effect-FC components. It provides comprehensive * support for multiple component definition patterns: * * - **Generator syntax** (yield* style): Most ergonomic and readable approach for sequential operations * - **Direct Effect return**: For simple components that return an Effect directly * - **Chained transformation functions**: Enables Effect.fn-style pipelines for composable transformations * - **Automatic tracing**: Optional tracing span creation with automatic `displayName` assignment * * When a `spanName` string is provided, the following occurs automatically: * 1. A distributed tracing span is created with the specified name * 2. The resulting React component receives `displayName = spanName` for DevTools visibility * * @example * ```tsx * const MyComponent = Component.make("MyComponent")(function* (props: { count: number }) { * const value = yield* someEffect * return

{value}
* }) * ``` * * @example As an opaque type using class syntax * ```tsx * class MyComponent extends Component.make("MyComponent")(function* (props: { count: number }) { * const value = yield* someEffect * return
{value}
* }) {} * ``` * * @example Without name * ```tsx * class MyComponent extends Component.make(function* (props: { count: number }) { * const value = yield* someEffect * return
{value}
* }) {} * ``` * * @example Using pipeline * ```tsx * class MyComponent extends Component.make("MyComponent")( * (props: { count: number }) => someEffect, * Effect.map(value =>
{value}
), * ) {} * ``` */ export const make: ( & make.Gen & make.NonGen & (( spanName: string, spanOptions?: Tracer.SpanOptions, ) => make.Gen & make.NonGen) ) = (spanNameOrBody: Function | string, ...pipeables: any[]): any => { if (typeof spanNameOrBody !== "string") { return Object.setPrototypeOf( Object.assign(function() {}, defaultOptions, { body: Effect.fn(spanNameOrBody as any, ...pipeables), }), ComponentPrototype, ) } else { const spanOptions = pipeables[0] return (body: any, ...pipeables: any[]) => Object.setPrototypeOf( Object.assign(function() {}, defaultOptions, { body: Effect.fn(spanNameOrBody, spanOptions)(body, ...pipeables as []), displayName: spanNameOrBody, }), ComponentPrototype, ) } } /** * Creates an Effect-FC Component without automatic distributed tracing. * * This function provides the same API surface as `make`, but does not create automatic tracing spans. * It follows the exact same overload structure as `Effect.fnUntraced`. * * Use this variant when you need: * - Full manual control over tracing instrumentation * - To reduce tracing overhead in deeply nested component hierarchies * - To avoid span noise in performance-sensitive applications * * When a `spanName` string is provided, it is used **exclusively** as the React component's * `displayName` for DevTools identification. No tracing span is created. * * @example * ```tsx * const MyComponent = Component.makeUntraced("MyComponent")(function* (props: { count: number }) { * const value = yield* someEffect * return
{value}
* }) * ``` * * @example As an opaque type using class syntax * ```tsx * class MyComponent extends Component.makeUntraced("MyComponent")(function* (props: { count: number }) { * const value = yield* someEffect * return
{value}
* }) {} * ``` * * @example Without name * ```tsx * class MyComponent extends Component.makeUntraced(function* (props: { count: number }) { * const value = yield* someEffect * return
{value}
* }) {} * ``` * * @example Using pipeline * ```tsx * class MyComponent extends Component.makeUntraced("MyComponent")( * (props: { count: number }) => someEffect, * Effect.map(value =>
{value}
), * ) {} * ``` */ export const makeUntraced: ( & make.Gen & make.NonGen & ((name: string) => make.Gen & make.NonGen) ) = (spanNameOrBody: Function | string, ...pipeables: any[]): any => ( typeof spanNameOrBody !== "string" ? Object.setPrototypeOf( Object.assign(function() {}, defaultOptions, { body: Effect.fnUntraced(spanNameOrBody as any, ...pipeables as []), }), ComponentPrototype, ) : (body: any, ...pipeables: any[]) => Object.setPrototypeOf( Object.assign(function() {}, defaultOptions, { body: Effect.fnUntraced(body, ...pipeables as []), displayName: spanNameOrBody, }), ComponentPrototype, ) ) export declare namespace withSignature { export type Signature = (props: any) => React.ReactNode export type Result< T extends Component, F extends Signature, > = Omit & { readonly use: Effect.Effect, Scope.Scope>> asFunctionComponent( runtimeRef: React.Ref, Scope.Scope>>> ): F } } export const withSignature: { (): >( self: T ) => withSignature.Result >( self: T ): withSignature.Result } = (self?: Component): any => self === undefined ? identity : self /** * Creates a new component with modified configuration options while preserving all original behavior. * * This function allows you to customize component-level options such as finalizer execution strategy * and debounce timing. * * @example * ```tsx * const MyComponentWithCustomOptions = MyComponent.pipe( * Component.withOptions({ * finalizerExecutionStrategy: ExecutionStrategy.parallel, * finalizerExecutionDebounce: "50 millis", * }) * ) * ``` */ export const withOptions: { >( options: Partial ): (self: T) => T >( self: T, options: Partial, ): T } = Function.dual(2, >( self: T, options: Partial, ): T => Object.setPrototypeOf( Object.assign(function() {}, self, options), Object.getPrototypeOf(self), )) /** * Wraps an Effect-FC Component and converts it into a standard React function component, * serving as an **entrypoint** into an Effect-FC component hierarchy. * * This is how Effect-FC components are integrated with the broader React ecosystem, * particularly when: * - Using client-side routers (TanStack Router, React Router, etc.) * - Implementing lazy-loaded or code-split routes * - Connecting to third-party libraries expecting standard React components * - Creating component boundaries between Effect-FC and non-Effect-FC code * * The Effect runtime is obtained from the provided React Context. * * @param self - The Effect-FC Component to be rendered as a standard React component * @param context - React Context providing the Effect Runtime for this component tree. * Create this using the `ReactRuntime` module. * * @example Integration with TanStack Router * ```tsx * // Application root * export const runtime = ReactRuntime.make(Layer.empty) * * function App() { * return ( * * * * ) * } * * // Route definition * export const Route = createFileRoute("/")({ * component: Component.withRuntime(HomePage, runtime.context) * }) * ``` * */ export const withRuntime: {

( context: React.Context>, ): (self: Component>) => (props: P) => A

( self: Component>, context: React.Context>, ): (props: P) => A } = Function.dual(2,

( self: Component, context: React.Context>, ) => function WithRuntime(props: P) { return React.createElement( Runtime.runSync(React.useContext(context))(self.use), props, ) }) /** * Internal Effect service that maintains a registry of scopes associated with React component instances. * * This service is used internally by the `useScope` hook to manage the lifecycle of component scopes, * including tracking active scopes and coordinating their cleanup when components unmount or dependencies change. */ export class ScopeMap extends Effect.Service()("@effect-fc/Component/ScopeMap", { effect: Effect.bind(Effect.Do, "ref", () => Ref.make(HashMap.empty())) }) {} export declare namespace ScopeMap { export interface Entry { readonly scope: Scope.CloseableScope readonly closeFiber: Option.Option> } } export declare namespace useScope { export interface Options { readonly finalizerExecutionStrategy?: ExecutionStrategy.ExecutionStrategy readonly finalizerExecutionDebounce?: Duration.DurationInput } } /** * Effect hook that creates and manages a `Scope` for the current component instance. * * This hook establishes a new scope that is automatically closed when: * - The component unmounts * - The dependency array `deps` changes * * The scope provides a resource management boundary for any Effects executed within the component, * ensuring proper cleanup of resources and execution of finalizers. * * @param deps - Dependency array following React.useEffect semantics. The scope is recreated * whenever any dependency changes. * @param options - Configuration for finalizer execution behavior, including execution strategy * and debounce timing. * * @returns An Effect that produces a `Scope` for resource management */ export const useScope = Effect.fnUntraced(function*( deps: React.DependencyList, options?: useScope.Options, ): Effect.fn.Return { // biome-ignore lint/style/noNonNullAssertion: context initialization const runtimeRef = React.useRef>(null!) runtimeRef.current = yield* Effect.runtime() const { key, scope } = React.useMemo(() => Runtime.runSync(runtimeRef.current)(Effect.Do.pipe( Effect.bind("scopeMapRef", () => Effect.map( ScopeMap as unknown as Effect.Effect, scopeMap => scopeMap.ref, )), Effect.let("key", () => ({})), Effect.bind("scope", () => Scope.make(options?.finalizerExecutionStrategy ?? defaultOptions.finalizerExecutionStrategy)), Effect.tap(({ scopeMapRef, key, scope }) => Ref.update(scopeMapRef, HashMap.set(key, { scope, closeFiber: Option.none(), })) ), // biome-ignore lint/correctness/useExhaustiveDependencies: use of React.DependencyList )), deps) // biome-ignore lint/correctness/useExhaustiveDependencies: only reactive on "key" React.useEffect(() => Runtime.runSync(runtimeRef.current)((ScopeMap as unknown as Effect.Effect).pipe( Effect.map(scopeMap => scopeMap.ref), Effect.tap(ref => ref.pipe( Effect.andThen(HashMap.get(key)), Effect.andThen(entry => Option.match(entry.closeFiber, { onSome: Fiber.interruptFork, onNone: () => Effect.void, })), )), Effect.map(ref => () => Runtime.runSync(runtimeRef.current)(Effect.andThen( Effect.sleep(options?.finalizerExecutionDebounce ?? defaultOptions.finalizerExecutionDebounce).pipe( Effect.andThen(Scope.close(scope, Exit.void)), Effect.onExit(() => Ref.update(ref, HashMap.remove(key))), Effect.forkDaemon, ), fiber => Ref.update(ref, HashMap.set(key, { scope, closeFiber: Option.some(fiber), })), )) ), )), [key]) return scope }) /** * Effect hook that executes an Effect once when the component mounts and caches the result. * * This hook is useful for one-time initialization logic that should not be re-executed * when the component re-renders. The Effect is executed exactly once during the component's * initial mount, and the cached result is returned on all subsequent renders. * * @param f - A function that returns the Effect to execute on mount * * @returns An Effect that produces the cached result of the Effect * * @example * ```tsx * const MyComponent = Component.make(function*() { * const initialData = yield* Component.useOnMount(() => getData) * return

{initialData}
* }) * ``` */ export const useOnMount = Effect.fnUntraced(function* ( f: () => Effect.Effect ): Effect.fn.Return { const runtime = yield* Effect.runtime() return yield* React.useState(() => Runtime.runSync(runtime)(Effect.cached(f())))[0] }) export declare namespace useOnChange { export interface Options extends useScope.Options {} } /** * Effect hook that executes an Effect whenever dependencies change and caches the result. * * This hook combines the dependency-tracking behavior of React.useEffect with Effect caching. * The Effect is re-executed whenever any dependency in the `deps` array changes, and the result * is cached until the next dependency change. * * A dedicated scope is created for each dependency change, ensuring proper resource cleanup: * - The scope closes when dependencies change * - The scope closes when the component unmounts * - All finalizers are executed according to the configured execution strategy * * @param f - A function that returns the Effect to execute * @param deps - Dependency array following React.useEffect semantics * @param options - Configuration for scope and finalizer behavior * * @returns An Effect that produces the cached result of the Effect * * @example * ```tsx * const MyComponent = Component.make(function* (props: { userId: string }) { * const userData = yield* Component.useOnChange( * getUser(props.userId), * [props.userId], * ) * return
{userData.name}
* }) * ``` */ export const useOnChange = Effect.fnUntraced(function* ( f: () => Effect.Effect, deps: React.DependencyList, options?: useOnChange.Options, ): Effect.fn.Return> { const runtime = yield* Effect.runtime>() const scope = yield* useScope(deps, options) // biome-ignore lint/correctness/useExhaustiveDependencies: only reactive on "scope" return yield* React.useMemo(() => Runtime.runSync(runtime)( Effect.cached(Effect.provideService(f(), Scope.Scope, scope)) ), [scope]) }) export declare namespace useReactEffect { export interface Options { readonly finalizerExecutionMode?: "sync" | "fork" readonly finalizerExecutionStrategy?: ExecutionStrategy.ExecutionStrategy } } /** * Effect hook that provides Effect-based semantics for React.useEffect. * * This hook bridges React's useEffect with the Effect system, allowing you to use Effects * for React side effects while maintaining React's dependency tracking and lifecycle semantics. * * Unlike React.useEffect which uses imperative cleanup functions, this hook leverages the * Effect Scope API for resource management. Cleanup logic is expressed declaratively through * finalizers registered with the scope, providing better composability and error handling. * * @param f - A function that returns an Effect to execute as a side effect * @param deps - Optional dependency array following React.useEffect semantics. * If omitted, the effect runs after every render. * @param options - Configuration for finalizer execution mode (sync or fork) and strategy * * @returns An Effect that produces void * * @example * ```tsx * const MyComponent = Component.make(function* (props: { id: string }) { * yield* Component.useReactEffect( * () => getNotificationStreamForUser(props.id).pipe( * Stream.unwrap, * Stream.runForEach(notification => Console.log(`Notification received: ${ notification }`), * Effect.forkScoped, * ), * [props.id], * ) * return
Subscribed to notifications for {props.id}
* }) * ``` */ export const useReactEffect = Effect.fnUntraced(function* ( f: () => Effect.Effect, deps?: React.DependencyList, options?: useReactEffect.Options, ): Effect.fn.Return> { const runtime = yield* Effect.runtime>() // biome-ignore lint/correctness/useExhaustiveDependencies: use of React.DependencyList React.useEffect(() => runReactEffect(runtime, f, options), deps) }) const runReactEffect = ( runtime: Runtime.Runtime>, f: () => Effect.Effect, options?: useReactEffect.Options, ) => Effect.Do.pipe( Effect.bind("scope", () => Scope.make(options?.finalizerExecutionStrategy ?? defaultOptions.finalizerExecutionStrategy)), Effect.bind("exit", ({ scope }) => Effect.exit(Effect.provideService(f(), Scope.Scope, scope))), Effect.map(({ scope }) => () => { switch (options?.finalizerExecutionMode ?? "fork") { case "sync": Runtime.runSync(runtime)(Scope.close(scope, Exit.void)) break case "fork": Runtime.runFork(runtime)(Scope.close(scope, Exit.void)) break } } ), Runtime.runSync(runtime), ) export declare namespace useReactLayoutEffect { export interface Options extends useReactEffect.Options {} } /** * Effect hook that provides Effect-based semantics for React.useLayoutEffect. * * This hook is identical to `useReactEffect` but executes synchronously after DOM mutations * but before the browser paints, following React.useLayoutEffect semantics. * * Use this hook when you need to: * - Measure DOM elements (e.g., for layout calculations) * - Synchronously update state based on DOM measurements * - Avoid visual flicker from asynchronous updates * * Like `useReactEffect`, cleanup logic is handled through the Effect Scope API rather than * imperative cleanup functions, providing declarative and composable resource management. * * @param f - A function that returns an Effect to execute as a layout side effect * @param deps - Optional dependency array following React.useLayoutEffect semantics. * If omitted, the effect runs after every render. * @param options - Configuration for finalizer execution mode (sync or fork) and strategy * * @returns An Effect that produces void * * @example * ```tsx * const MyComponent = Component.make(function*() { * const ref = React.useRef(null) * yield* Component.useReactLayoutEffect( * () => Effect.gen(function* () { * const element = ref.current * if (element) { * const rect = element.getBoundingClientRect() * yield* Console.log(`Element dimensions: ${ rect.width }x${ rect.height }`) * } * }), * [], * ) * return
Content
* }) * ``` */ export const useReactLayoutEffect = Effect.fnUntraced(function* ( f: () => Effect.Effect, deps?: React.DependencyList, options?: useReactLayoutEffect.Options, ): Effect.fn.Return> { const runtime = yield* Effect.runtime>() // biome-ignore lint/correctness/useExhaustiveDependencies: use of React.DependencyList React.useLayoutEffect(() => runReactEffect(runtime, f, options), deps) }) /** * Effect hook that provides a synchronous function to execute Effects within the current runtime context. * * This hook returns a function that can execute Effects synchronously, blocking until completion. * Use this when you need to run Effects from non-Effect code (e.g., event handlers, callbacks) * within a component. * * @returns An Effect that produces a function capable of synchronously executing Effects * * @example * ```tsx * const MyComponent = Component.make(function*() { * const runSync = yield* Component.useRunSync() // Specify required services * const runSync = yield* Component.useRunSync() // Or no service requirements * * return * }) * ``` */ export const useRunSync = (): Effect.Effect< (effect: Effect.Effect) => A, never, Scope.Scope | R > => Effect.andThen(Effect.runtime(), Runtime.runSync) /** * Effect hook that provides an asynchronous function to execute Effects within the current runtime context. * * This hook returns a function that executes Effects asynchronously, returning a Promise that resolves * with the Effect's result. Use this when you need to run Effects from non-Effect code (e.g., event handlers, * async callbacks) and want to handle the result asynchronously. * * @returns An Effect that produces a function capable of asynchronously executing Effects * * @example * ```tsx * const MyComponent = Component.make(function*() { * const runPromise = yield* Component.useRunPromise() // Specify required services * const runPromise = yield* Component.useRunPromise() // Or no service requirements * * return * }) * ``` */ export const useRunPromise = (): Effect.Effect< (effect: Effect.Effect) => Promise, never, Scope.Scope | R > => Effect.andThen(Effect.runtime(), context => Runtime.runPromise(context)) /** * Effect hook that memoizes a function that returns an Effect, providing synchronous execution. * * This hook wraps a function that returns an Effect and returns a memoized version that: * - Executes the Effect synchronously when called * - Is memoized based on the provided dependency array * - Maintains referential equality across renders when dependencies don't change * * Use this to create stable callback references for event handlers and other scenarios * where you need to execute Effects synchronously from non-Effect code. * * @param f - A function that accepts arguments and returns an Effect * @param deps - Dependency array. The memoized function is recreated when dependencies change. * * @returns An Effect that produces a memoized function with the same signature as `f` * * @example * ```tsx * const MyComponent = Component.make(function* (props: { onSave: (data: Data) => void }) { * const handleSave = yield* Component.useCallbackSync( * (data: Data) => Effect.sync(() => props.onSave(data)), * [props.onSave], * ) * * return * }) * ``` */ export const useCallbackSync = Effect.fnUntraced(function* ( f: (...args: Args) => Effect.Effect, deps: React.DependencyList, ): Effect.fn.Return<(...args: Args) => A, never, R> { // biome-ignore lint/style/noNonNullAssertion: context initialization const runtimeRef = React.useRef>(null!) runtimeRef.current = yield* Effect.runtime() // biome-ignore lint/correctness/useExhaustiveDependencies: use of React.DependencyList return React.useCallback((...args: Args) => Runtime.runSync(runtimeRef.current)(f(...args)), deps) }) /** * Effect hook that memoizes a function that returns an Effect, providing asynchronous execution. * * This hook wraps a function that returns an Effect and returns a memoized version that: * - Executes the Effect asynchronously when called, returning a Promise * - Is memoized based on the provided dependency array * - Maintains referential equality across renders when dependencies don't change * * Use this to create stable callback references for async event handlers and other scenarios * where you need to execute Effects asynchronously from non-Effect code. * * @param f - A function that accepts arguments and returns an Effect * @param deps - Dependency array. The memoized function is recreated when dependencies change. * * @returns An Effect that produces a memoized function that returns a Promise * * @example * ```tsx * const MyComponent = Component.make(function* (props: { onSave: (data: Data) => void }) { * const handleSave = yield* Component.useCallbackPromise( * (data: Data) => Effect.promise(() => props.onSave(data)), * [props.onSave], * ) * * return * }) * ``` */ export const useCallbackPromise = Effect.fnUntraced(function* ( f: (...args: Args) => Effect.Effect, deps: React.DependencyList, ): Effect.fn.Return<(...args: Args) => Promise, never, R> { // biome-ignore lint/style/noNonNullAssertion: context initialization const runtimeRef = React.useRef>(null!) runtimeRef.current = yield* Effect.runtime() // biome-ignore lint/correctness/useExhaustiveDependencies: use of React.DependencyList return React.useCallback((...args: Args) => Runtime.runPromise(runtimeRef.current)(f(...args)), deps) }) export declare namespace useContext { export interface Options extends useOnChange.Options {} } /** * Effect hook that constructs an Effect Layer and returns the resulting context. * * This hook creates a managed runtime from the provided layer and returns the context it produces. * The layer is reconstructed whenever its value changes, so ensure the layer reference is stable * (typically by memoizing it or defining it outside the component). * * The hook automatically manages the layer's lifecycle: * - The layer is built when the component mounts or when the layer reference changes * - Resources are properly released when the component unmounts or dependencies change * - Finalizers are executed according to the configured execution strategy * * @param layer - The Effect Layer to construct. Should be a stable reference to avoid unnecessary * reconstruction. Consider memoizing with React.useMemo if defined inline. * @param options - Configuration for scope and finalizer behavior * * @returns An Effect that produces the context created by the layer * * @throws If the layer contains asynchronous effects, the component must be wrapped with `Async.async` * * @example * ```tsx * const MyLayer = Layer.succeed(MyService, new MyServiceImpl()) * const MyComponent = Component.make(function*() { * const context = yield* Component.useContextFromLayer(MyLayer) * const Sub = yield* SubComponent.use.pipe( * Effect.provide(context) * ) * * return * }) * ``` * * @example With memoized layer * ```tsx * const MyComponent = Component.make(function*(props: { id: string })) { * const context = yield* Component.useContextFromLayer( * React.useMemo(() => Layer.succeed(MyService, new MyServiceImpl(props.id)), [props.id]) * ) * const Sub = yield* SubComponent.use.pipe( * Effect.provide(context) * ) * * return * }) * ``` * * @example With async layer * ```tsx * const MyAsyncLayer = Layer.effect(MyService, someAsyncEffect) * const MyComponent = Component.make(function*() { * const context = yield* Component.useContextFromLayer(MyAsyncLayer) * const Sub = yield* SubComponent.use.pipe( * Effect.provide(context) * ) * * return * }).pipe( * Async.async // Required to handle async layer effects * ) */ export const useContextFromLayer = ( layer: Layer.Layer, options?: useContext.Options, ): Effect.Effect, E, RIn | Scope.Scope> => useOnChange(() => Effect.context().pipe( Effect.map(context => ManagedRuntime.make(Layer.provide(layer, Layer.succeedContext(context)))), Effect.tap(runtime => Effect.addFinalizer(() => runtime.disposeEffect)), Effect.andThen(runtime => runtime.runtimeEffect), Effect.andThen(runtime => runtime.context), ), [layer], options)