Add Component doc
All checks were successful
Lint / lint (push) Successful in 42s

This commit is contained in:
Julien Valverdé
2026-01-22 02:28:18 +01:00
parent 49c79295d6
commit 3be9d94aa8

View File

@@ -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),