> {
const context = yield* Effect.context>()
const scope = yield* useScope(deps, options)
// biome-ignore lint/correctness/useExhaustiveDependencies: only reactive on "scope"
return yield* React.useMemo(() => Effect.runSyncWith(context)(
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 context = yield* Effect.context>()
// biome-ignore lint/correctness/useExhaustiveDependencies: use of React.DependencyList
React.useEffect(() => runReactEffect(context, f, options), deps)
})
const runReactEffect = (
context: Context.Context>,
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":
Effect.runSyncWith(context)(Scope.close(scope, Exit.void))
break
case "fork":
Effect.runForkWith(context)(Scope.close(scope, Exit.void))
break
}
}
),
Effect.runSyncWith(context),
)
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 context = yield* Effect.context>()
// biome-ignore lint/correctness/useExhaustiveDependencies: use of React.DependencyList
React.useLayoutEffect(() => runReactEffect(context, 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 runSync(someEffect)}>Click me
* })
* ```
*/
export const useRunSync = (): Effect.Effect<
(effect: Effect.Effect ) => A,
never,
Scope.Scope | R
> => Effect.map(Effect.context(), Effect.runSyncWith)
/**
* 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 runPromise(someEffect)}>Click me
* })
* ```
*/
export const useRunPromise = (): Effect.Effect<
(effect: Effect.Effect ) => Promise ,
never,
Scope.Scope | R
> => Effect.map(Effect.context(), Effect.runPromiseWith)
/**
* 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 handleSave(myData)}>Save
* })
* ```
*/
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 contextRef = React.useRef>(null!)
contextRef.current = yield* Effect.context()
// biome-ignore lint/correctness/useExhaustiveDependencies: use of React.DependencyList
return React.useCallback((...args: Args) => Effect.runSyncWith(contextRef.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 handleSave(myData)}>Save
* })
* ```
*/
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 contextRef = React.useRef>(null!)
contextRef.current = yield* Effect.context()
// biome-ignore lint/correctness/useExhaustiveDependencies: use of React.DependencyList
return React.useCallback((...args: Args) => Effect.runPromiseWith(contextRef.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.flatMap(
Effect.context(),
context => Layer.build(Layer.provide(layer, Layer.succeedContext(context))),
), [layer], options)