This commit is contained in:
@@ -47,16 +47,18 @@ export declare namespace Component {
|
||||
* Options that can be set on the component
|
||||
*/
|
||||
export interface Options {
|
||||
/** Custom displayName for React DevTools and debugging */
|
||||
/** Custom displayName for React DevTools and debugging. */
|
||||
readonly displayName?: string
|
||||
|
||||
/**
|
||||
* Strategy used when executing finalizers on unmount/scope close
|
||||
* Strategy used when executing finalizers on unmount/scope close.
|
||||
* @default ExecutionStrategy.sequential
|
||||
*/
|
||||
readonly finalizerExecutionStrategy: ExecutionStrategy.ExecutionStrategy
|
||||
|
||||
/**
|
||||
* Debounce time before executing finalizers after component unmount
|
||||
* Helps avoid unnecessary work during fast remount/remount cycles
|
||||
* Debounce time before executing finalizers after component unmount.
|
||||
* Helps avoid unnecessary work during fast remount/remount cycles.
|
||||
* @default "100 millis"
|
||||
*/
|
||||
readonly finalizerExecutionDebounce: Duration.DurationInput
|
||||
@@ -338,9 +340,17 @@ export declare namespace make {
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an Effect-based React component.
|
||||
* Creates an Effect-FC Component following the same overloads and pipeline style as `Effect.fn`.
|
||||
*
|
||||
* Follows the `Effect.fn` API. Supports both generator syntax (recommended) and direct Effect composition.
|
||||
* This is the **recommended** way to define components. It supports:
|
||||
* - Generator syntax (yield* style) — most ergonomic and readable
|
||||
* - Direct Effect return (non-generator)
|
||||
* - Chained transformation functions (like Effect.fn pipelines)
|
||||
* - Optional tracing span with automatic `displayName`
|
||||
*
|
||||
* When you provide a `spanName` as the first argument, two things happen automatically:
|
||||
* 1. A tracing span is created with that name (unless using `makeUntraced`)
|
||||
* 2. The resulting React component gets `displayName = spanName`
|
||||
*/
|
||||
export const make: (
|
||||
& make.Gen
|
||||
@@ -371,10 +381,15 @@ export const make: (
|
||||
}
|
||||
|
||||
/**
|
||||
* Same as `make` but creates an untraced version (no automatic span created).
|
||||
* Useful for very low-level utilities or when you want full control over tracing.
|
||||
* Same as `make`, but creates an **untraced** version — no automatic tracing span is created.
|
||||
*
|
||||
* Follows the `Effect.fnUntraced` API.
|
||||
* Follows the exact same API shape as `Effect.fnUntraced`.
|
||||
* Useful for:
|
||||
* - Components where you want full manual control over tracing
|
||||
* - Avoiding span noise in deeply nested UI
|
||||
*
|
||||
* When a string is provided as first argument, it is **only** used as the React component's `displayName`
|
||||
* (no tracing span is created).
|
||||
*/
|
||||
export const makeUntraced: (
|
||||
& make.Gen
|
||||
@@ -442,7 +457,7 @@ export const withOptions: {
|
||||
*
|
||||
* // Route
|
||||
* export const Route = createFileRoute("/")({
|
||||
* component: withRuntime(HomePage, runtime.context),
|
||||
* component: Component.withRuntime(HomePage, runtime.context)
|
||||
* })
|
||||
* ```
|
||||
*
|
||||
@@ -468,6 +483,10 @@ export const withRuntime: {
|
||||
})
|
||||
|
||||
|
||||
/**
|
||||
* Service that keeps track of scopes associated with React components
|
||||
* (used internally by the `useScope` hook).
|
||||
*/
|
||||
export class ScopeMap extends Effect.Service<ScopeMap>()("@effect-fc/Component/ScopeMap", {
|
||||
effect: Effect.bind(Effect.Do, "ref", () => Ref.make(HashMap.empty<object, ScopeMap.Entry>()))
|
||||
}) {}
|
||||
@@ -487,6 +506,14 @@ export declare namespace useScope {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Hook that creates and manages a `Scope` for the current component instance.
|
||||
*
|
||||
* Automatically closes the scope whenever `deps` changes or the component unmounts.
|
||||
*
|
||||
* @param deps - dependency array like in `React.useEffect`
|
||||
* @param options - finalizer execution control
|
||||
*/
|
||||
export const useScope = Effect.fnUntraced(function*(
|
||||
deps: React.DependencyList,
|
||||
options?: useScope.Options,
|
||||
@@ -542,6 +569,9 @@ export const useScope = Effect.fnUntraced(function*(
|
||||
return scope
|
||||
})
|
||||
|
||||
/**
|
||||
* Runs an effect and returns its result only once on component mount.
|
||||
*/
|
||||
export const useOnMount = Effect.fnUntraced(function* <A, E, R>(
|
||||
f: () => Effect.Effect<A, E, R>
|
||||
): Effect.fn.Return<A, E, R> {
|
||||
@@ -553,6 +583,11 @@ export declare namespace useOnChange {
|
||||
export interface Options extends useScope.Options {}
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs an effect and returns its result whenever dependencies change.
|
||||
*
|
||||
* Provides its own `Scope` which closes whenever `deps` changes or the component unmounts.
|
||||
*/
|
||||
export const useOnChange = Effect.fnUntraced(function* <A, E, R>(
|
||||
f: () => Effect.Effect<A, E, R>,
|
||||
deps: React.DependencyList,
|
||||
@@ -574,6 +609,11 @@ export declare namespace useReactEffect {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Like `React.useEffect` but accepts an effect.
|
||||
*
|
||||
* Cleanup logic is handled through the `Scope` API rather than using imperative cleanup.
|
||||
*/
|
||||
export const useReactEffect = Effect.fnUntraced(function* <E, R>(
|
||||
f: () => Effect.Effect<void, E, R>,
|
||||
deps?: React.DependencyList,
|
||||
@@ -610,6 +650,11 @@ export declare namespace useReactLayoutEffect {
|
||||
export interface Options extends useReactEffect.Options {}
|
||||
}
|
||||
|
||||
/**
|
||||
* Like `React.useReactLayoutEffect` but accepts an effect.
|
||||
*
|
||||
* Cleanup logic is handled through the `Scope` API rather than using imperative cleanup.
|
||||
*/
|
||||
export const useReactLayoutEffect = Effect.fnUntraced(function* <E, R>(
|
||||
f: () => Effect.Effect<void, E, R>,
|
||||
deps?: React.DependencyList,
|
||||
@@ -620,18 +665,27 @@ export const useReactLayoutEffect = Effect.fnUntraced(function* <E, R>(
|
||||
React.useLayoutEffect(() => runReactEffect(runtime, f, options), deps)
|
||||
})
|
||||
|
||||
/**
|
||||
* Get a synchronous run function for the current runtime context.
|
||||
*/
|
||||
export const useRunSync = <R = never>(): Effect.Effect<
|
||||
<A, E = never>(effect: Effect.Effect<A, E, Scope.Scope | R>) => A,
|
||||
never,
|
||||
Scope.Scope | R
|
||||
> => Effect.andThen(Effect.runtime(), Runtime.runSync)
|
||||
|
||||
/**
|
||||
* Get a Promise-based run function for the current runtime context.
|
||||
*/
|
||||
export const useRunPromise = <R = never>(): Effect.Effect<
|
||||
<A, E = never>(effect: Effect.Effect<A, E, Scope.Scope | R>) => Promise<A>,
|
||||
never,
|
||||
Scope.Scope | R
|
||||
> => Effect.andThen(Effect.runtime(), context => Runtime.runPromise(context))
|
||||
|
||||
/**
|
||||
* Turns a function returning an effect into a memoized synchronous function.
|
||||
*/
|
||||
export const useCallbackSync = Effect.fnUntraced(function* <Args extends unknown[], A, E, R>(
|
||||
f: (...args: Args) => Effect.Effect<A, E, R>,
|
||||
deps: React.DependencyList,
|
||||
@@ -644,6 +698,9 @@ export const useCallbackSync = Effect.fnUntraced(function* <Args extends unknown
|
||||
return React.useCallback((...args: Args) => Runtime.runSync(runtimeRef.current)(f(...args)), deps)
|
||||
})
|
||||
|
||||
/**
|
||||
* Turns a function returning an effect into a memoized Promise-based asynchronous function.
|
||||
*/
|
||||
export const useCallbackPromise = Effect.fnUntraced(function* <Args extends unknown[], A, E, R>(
|
||||
f: (...args: Args) => Effect.Effect<A, E, R>,
|
||||
deps: React.DependencyList,
|
||||
@@ -660,10 +717,17 @@ export declare namespace useContext {
|
||||
export interface Options extends useOnChange.Options {}
|
||||
}
|
||||
|
||||
/**
|
||||
* Hook that constructs a layer and returns the created context.
|
||||
*
|
||||
* The layer gets reconstructed everytime `layer` changes, so make sure its value is stable.
|
||||
*
|
||||
* Building a layer containing asynchronous effects require the component calling this hook to be made async using `Async.async`.
|
||||
*/
|
||||
export const useContext = <ROut, E, RIn>(
|
||||
layer: Layer.Layer<ROut, E, RIn>,
|
||||
options?: useContext.Options,
|
||||
): Effect.Effect<Context.Context<ROut>, E, Scope.Scope | RIn> => useOnChange(() => Effect.context<RIn>().pipe(
|
||||
): Effect.Effect<Context.Context<ROut>, E, Exclude<RIn, Scope.Scope>> => useOnChange(() => Effect.context<RIn>().pipe(
|
||||
Effect.map(context => ManagedRuntime.make(Layer.provide(layer, Layer.succeedContext(context)))),
|
||||
Effect.tap(runtime => Effect.addFinalizer(() => runtime.disposeEffect)),
|
||||
Effect.andThen(runtime => runtime.runtimeEffect),
|
||||
|
||||
Reference in New Issue
Block a user