This commit is contained in:
@@ -8,11 +8,7 @@ export const TypeId: unique symbol = Symbol.for("@effect-fc/Component/Component"
|
|||||||
export type TypeId = typeof TypeId
|
export type TypeId = typeof TypeId
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Interface representing an Effect-based React Component.
|
* Represents an Effect-based React Component that integrates the Effect system with React.
|
||||||
*
|
|
||||||
* This is both:
|
|
||||||
* - an Effect that produces a React function component
|
|
||||||
* - a constructor-like object with component metadata and options
|
|
||||||
*/
|
*/
|
||||||
export interface Component<P extends {}, A extends React.ReactNode, E, R>
|
export interface Component<P extends {}, A extends React.ReactNode, E, R>
|
||||||
extends ComponentPrototype<P, A, R>, ComponentOptions {
|
extends ComponentPrototype<P, A, R>, ComponentOptions {
|
||||||
@@ -98,18 +94,25 @@ const use = Effect.fnUntraced(function* <P extends {}, A extends React.ReactNode
|
|||||||
|
|
||||||
|
|
||||||
export interface ComponentOptions {
|
export interface ComponentOptions {
|
||||||
/** Custom displayName for React DevTools and debugging. */
|
/**
|
||||||
|
* Custom display name for the component in React DevTools and debugging utilities.
|
||||||
|
* Improves developer experience by providing meaningful component identification.
|
||||||
|
*/
|
||||||
readonly displayName?: string
|
readonly displayName?: string
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Strategy used when executing finalizers on unmount/scope close.
|
* 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
|
* @default ExecutionStrategy.sequential
|
||||||
*/
|
*/
|
||||||
readonly finalizerExecutionStrategy: ExecutionStrategy.ExecutionStrategy
|
readonly finalizerExecutionStrategy: ExecutionStrategy.ExecutionStrategy
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Debounce time before executing finalizers after component unmount.
|
* Debounce duration before executing finalizers after component unmount.
|
||||||
* Helps avoid unnecessary work during fast remount/remount cycles.
|
* Prevents unnecessary cleanup work during rapid remount/unmount cycles,
|
||||||
|
* which is common in development and certain UI patterns.
|
||||||
|
*
|
||||||
* @default "100 millis"
|
* @default "100 millis"
|
||||||
*/
|
*/
|
||||||
readonly finalizerExecutionDebounce: Duration.DurationInput
|
readonly finalizerExecutionDebounce: Duration.DurationInput
|
||||||
@@ -352,17 +355,42 @@ export declare namespace make {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates an Effect-FC Component following the same overloads and pipeline style as `Effect.fn`.
|
* Creates an Effect-FC Component using the same overloads and pipeline composition style as `Effect.fn`.
|
||||||
*
|
*
|
||||||
* This is the **recommended** way to define components. It supports:
|
* This is the **recommended** approach for defining Effect-FC components. It provides comprehensive
|
||||||
* - Generator syntax (yield* style) — most ergonomic and readable
|
* support for multiple component definition patterns:
|
||||||
* - 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:
|
* - **Generator syntax** (yield* style): Most ergonomic and readable approach for sequential operations
|
||||||
* 1. A tracing span is created with that name (unless using `makeUntraced`)
|
* - **Direct Effect return**: For simple components that return an Effect directly
|
||||||
* 2. The resulting React component gets `displayName = spanName`
|
* - **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 as the first argument, 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 <div>{value}</div>
|
||||||
|
* })
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* @example As an opaque type using class syntax
|
||||||
|
* ```tsx
|
||||||
|
* class MyComponent extends Component.make("MyComponent")(function* (props: { count: number }) {
|
||||||
|
* const value = yield* someEffect
|
||||||
|
* return <div>{value}</div>
|
||||||
|
* })
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* @example Without name
|
||||||
|
* ```tsx
|
||||||
|
* const MyComponent = Component.make((props: { count: number }) => {
|
||||||
|
* return Effect.sync(() => <div>{props.count}</div>)
|
||||||
|
* })
|
||||||
|
* ```
|
||||||
*/
|
*/
|
||||||
export const make: (
|
export const make: (
|
||||||
& make.Gen
|
& make.Gen
|
||||||
@@ -393,15 +421,33 @@ export const make: (
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Same as `make`, but creates an **untraced** version — no automatic tracing span is created.
|
* Creates an Effect-FC Component without automatic distributed tracing.
|
||||||
*
|
*
|
||||||
* Follows the exact same API shape as `Effect.fnUntraced`.
|
* This function provides the same API surface as `make`, but does not create automatic tracing spans.
|
||||||
* Useful for:
|
* It follows the exact same overload structure as `Effect.fnUntraced`.
|
||||||
* - 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`
|
* Use this variant when you need:
|
||||||
* (no tracing span is created).
|
* - 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 string is provided as the first argument, it is used **exclusively** as the React component's
|
||||||
|
* `displayName` for DevTools identification. No tracing span is created.
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* ```tsx
|
||||||
|
* const MyComponent = Component.makeUntraced(function* (props: { count: number }) {
|
||||||
|
* const value = yield* someEffect
|
||||||
|
* return <div>{value}</div>
|
||||||
|
* })
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* @example With displayName only
|
||||||
|
* ```tsx
|
||||||
|
* const MyComponent = Component.makeUntraced("MyComponent", (props: { count: number }) => {
|
||||||
|
* return Effect.sync(() => <div>{props.count}</div>)
|
||||||
|
* })
|
||||||
|
* ```
|
||||||
*/
|
*/
|
||||||
export const makeUntraced: (
|
export const makeUntraced: (
|
||||||
& make.Gen
|
& make.Gen
|
||||||
@@ -425,7 +471,21 @@ export const makeUntraced: (
|
|||||||
)
|
)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new component with modified options while preserving original behavior.
|
* 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 MyComponent = Component.make(...)
|
||||||
|
* const MyComponentWithCustomOptions = MyComponent.pipe(
|
||||||
|
* Component.withOptions({
|
||||||
|
* finalizerExecutionStrategy: ExecutionStrategy.parallel,
|
||||||
|
* finalizerExecutionDebounce: "50 millis"
|
||||||
|
* })
|
||||||
|
* )
|
||||||
|
* ```
|
||||||
*/
|
*/
|
||||||
export const withOptions: {
|
export const withOptions: {
|
||||||
<T extends Component<any, any, any, any>>(
|
<T extends Component<any, any, any, any>>(
|
||||||
@@ -444,21 +504,30 @@ export const withOptions: {
|
|||||||
))
|
))
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Wraps an Effect-FC `Component` and turns it into a regular React function component
|
* Wraps an Effect-FC Component and converts it into a standard React function component,
|
||||||
* that serves as an **entrypoint** into an Effect-FC component hierarchy.
|
* serving as an **entrypoint** into an Effect-FC component hierarchy.
|
||||||
*
|
*
|
||||||
* This is the recommended way to connect Effect-FC components to the rest of your React app,
|
* This is the recommended approach for integrating Effect-FC components with the broader React ecosystem,
|
||||||
* especially when using routers (TanStack Router, React Router, etc.), lazy-loaded routes,
|
* particularly when:
|
||||||
* or any place where a standard React component is expected.
|
* - 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 runtime is obtained from the provided React Context, allowing you to:
|
* The Effect runtime is obtained from the provided React Context, enabling:
|
||||||
* - Provide dependencies once at a high level
|
* - Single-point dependency injection at the application root
|
||||||
* - Use the same runtime across an entire route tree or feature
|
* - Consistent runtime environment across entire route trees or feature modules
|
||||||
|
* - Proper resource lifecycle management across component hierarchies
|
||||||
*
|
*
|
||||||
* @example Using TanStack Router
|
* @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
|
* ```tsx
|
||||||
* // Main
|
* // Application root
|
||||||
* export const runtime = ReactRuntime.make(Layer.empty)
|
* export const runtime = ReactRuntime.make(Layer.empty)
|
||||||
|
*
|
||||||
* function App() {
|
* function App() {
|
||||||
* return (
|
* return (
|
||||||
* <ReactRuntime.Provider runtime={runtime}>
|
* <ReactRuntime.Provider runtime={runtime}>
|
||||||
@@ -467,14 +536,12 @@ export const withOptions: {
|
|||||||
* )
|
* )
|
||||||
* }
|
* }
|
||||||
*
|
*
|
||||||
* // Route
|
* // Route definition
|
||||||
* export const Route = createFileRoute("/")({
|
* export const Route = createFileRoute("/")({
|
||||||
* component: Component.withRuntime(HomePage, runtime.context)
|
* component: Component.withRuntime(HomePage, runtime.context)
|
||||||
* })
|
* })
|
||||||
* ```
|
* ```
|
||||||
*
|
*
|
||||||
* @param self - The Effect-FC Component you want to render as a regular React component.
|
|
||||||
* @param context - React Context that holds the Runtime to use for this component tree. See the `ReactRuntime` module to create one.
|
|
||||||
*/
|
*/
|
||||||
export const withRuntime: {
|
export const withRuntime: {
|
||||||
<P extends {}, A extends React.ReactNode, E, R>(
|
<P extends {}, A extends React.ReactNode, E, R>(
|
||||||
@@ -496,8 +563,10 @@ export const withRuntime: {
|
|||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Service that keeps track of scopes associated with React components
|
* Internal Effect service that maintains a registry of scopes associated with React component instances.
|
||||||
* (used internally by the `useScope` hook).
|
*
|
||||||
|
* 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<ScopeMap>()("@effect-fc/Component/ScopeMap", {
|
export class ScopeMap extends Effect.Service<ScopeMap>()("@effect-fc/Component/ScopeMap", {
|
||||||
effect: Effect.bind(Effect.Do, "ref", () => Ref.make(HashMap.empty<object, ScopeMap.Entry>()))
|
effect: Effect.bind(Effect.Do, "ref", () => Ref.make(HashMap.empty<object, ScopeMap.Entry>()))
|
||||||
@@ -519,13 +588,22 @@ export declare namespace useScope {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Hook that creates and manages a `Scope` for the current component instance.
|
* Effect hook that creates and manages a `Scope` for the current component instance.
|
||||||
*
|
*
|
||||||
* Automatically closes the scope whenever `deps` changes or the component unmounts.
|
* This hook establishes a new scope that is automatically closed when:
|
||||||
|
* - The component unmounts
|
||||||
|
* - The dependency array `deps` changes
|
||||||
*
|
*
|
||||||
* @param deps - dependency array like in `React.useEffect`
|
* The scope provides a resource management boundary for any Effects executed within the component,
|
||||||
* @param options - finalizer execution control
|
* 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*(
|
export const useScope = Effect.fnUntraced(function*(
|
||||||
deps: React.DependencyList,
|
deps: React.DependencyList,
|
||||||
options?: useScope.Options,
|
options?: useScope.Options,
|
||||||
@@ -579,7 +657,23 @@ export const useScope = Effect.fnUntraced(function*(
|
|||||||
})
|
})
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Runs an effect and returns its result only once on component mount.
|
* 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* (props) {
|
||||||
|
* const initialData = yield* useOnMount(() => fetchData)
|
||||||
|
* return <div>{initialData}</div>
|
||||||
|
* })
|
||||||
|
* ```
|
||||||
*/
|
*/
|
||||||
export const useOnMount = Effect.fnUntraced(function* <A, E, R>(
|
export const useOnMount = Effect.fnUntraced(function* <A, E, R>(
|
||||||
f: () => Effect.Effect<A, E, R>
|
f: () => Effect.Effect<A, E, R>
|
||||||
@@ -593,9 +687,33 @@ export declare namespace useOnChange {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Runs an effect and returns its result whenever dependencies change.
|
* Effect hook that executes an Effect whenever dependencies change and caches the result.
|
||||||
*
|
*
|
||||||
* Provides its own `Scope` which closes whenever `deps` changes or the component unmounts.
|
* 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* useOnChange(
|
||||||
|
* fetchUser(props.userId),
|
||||||
|
* [props.userId],
|
||||||
|
* )
|
||||||
|
* return <div>{userData.name}</div>
|
||||||
|
* })
|
||||||
|
* ```
|
||||||
*/
|
*/
|
||||||
export const useOnChange = Effect.fnUntraced(function* <A, E, R>(
|
export const useOnChange = Effect.fnUntraced(function* <A, E, R>(
|
||||||
f: () => Effect.Effect<A, E, R>,
|
f: () => Effect.Effect<A, E, R>,
|
||||||
@@ -619,9 +737,35 @@ export declare namespace useReactEffect {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Like `React.useEffect` but accepts an effect.
|
* Effect hook that provides Effect-based semantics for React.useEffect.
|
||||||
*
|
*
|
||||||
* Cleanup logic is handled through the `Scope` API rather than using imperative cleanup.
|
* This hook bridges React's useEffect with the Effect system, allowing you to use Effects
|
||||||
|
* for 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* useReactEffect(
|
||||||
|
* () => {
|
||||||
|
* const subscription = subscribe(props.id)
|
||||||
|
* return Effect.addFinalizer(() => subscription.unsubscribe())
|
||||||
|
* },
|
||||||
|
* [props.id]
|
||||||
|
* )
|
||||||
|
* return <div>Subscribed to {props.id}</div>
|
||||||
|
* })
|
||||||
|
* ```
|
||||||
*/
|
*/
|
||||||
export const useReactEffect = Effect.fnUntraced(function* <E, R>(
|
export const useReactEffect = Effect.fnUntraced(function* <E, R>(
|
||||||
f: () => Effect.Effect<void, E, R>,
|
f: () => Effect.Effect<void, E, R>,
|
||||||
@@ -660,9 +804,43 @@ export declare namespace useReactLayoutEffect {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Like `React.useReactLayoutEffect` but accepts an effect.
|
* Effect hook that provides Effect-based semantics for React.useLayoutEffect.
|
||||||
*
|
*
|
||||||
* Cleanup logic is handled through the `Scope` API rather than using imperative cleanup.
|
* 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* (props) {
|
||||||
|
* const ref = React.useRef<HTMLDivElement>(null)
|
||||||
|
* yield* useReactLayoutEffect(
|
||||||
|
* () => {
|
||||||
|
* if (ref.current) {
|
||||||
|
* const height = ref.current.offsetHeight
|
||||||
|
* // Perform layout-dependent operations
|
||||||
|
* }
|
||||||
|
* return Effect.void
|
||||||
|
* },
|
||||||
|
* []
|
||||||
|
* )
|
||||||
|
* return <div ref={ref}>Content</div>
|
||||||
|
* })
|
||||||
|
* ```
|
||||||
*/
|
*/
|
||||||
export const useReactLayoutEffect = Effect.fnUntraced(function* <E, R>(
|
export const useReactLayoutEffect = Effect.fnUntraced(function* <E, R>(
|
||||||
f: () => Effect.Effect<void, E, R>,
|
f: () => Effect.Effect<void, E, R>,
|
||||||
@@ -675,7 +853,25 @@ export const useReactLayoutEffect = Effect.fnUntraced(function* <E, R>(
|
|||||||
})
|
})
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get a synchronous run function for the current runtime context.
|
* 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* (props) {
|
||||||
|
* const runSync = yield* useRunSync()
|
||||||
|
* const handleClick = () => {
|
||||||
|
* const result = runSync(someEffect)
|
||||||
|
* console.log(result)
|
||||||
|
* }
|
||||||
|
* return <button onClick={handleClick}>Click me</button>
|
||||||
|
* })
|
||||||
|
* ```
|
||||||
*/
|
*/
|
||||||
export const useRunSync = <R = never>(): Effect.Effect<
|
export const useRunSync = <R = never>(): Effect.Effect<
|
||||||
<A, E = never>(effect: Effect.Effect<A, E, Scope.Scope | R>) => A,
|
<A, E = never>(effect: Effect.Effect<A, E, Scope.Scope | R>) => A,
|
||||||
@@ -684,7 +880,29 @@ export const useRunSync = <R = never>(): Effect.Effect<
|
|||||||
> => Effect.andThen(Effect.runtime(), Runtime.runSync)
|
> => Effect.andThen(Effect.runtime(), Runtime.runSync)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get a Promise-based run function for the current runtime context.
|
* 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* (props) {
|
||||||
|
* const runPromise = yield* useRunPromise()
|
||||||
|
* const handleClick = async () => {
|
||||||
|
* try {
|
||||||
|
* const result = await runPromise(someEffect)
|
||||||
|
* console.log(result)
|
||||||
|
* } catch (error) {
|
||||||
|
* console.error(error)
|
||||||
|
* }
|
||||||
|
* }
|
||||||
|
* return <button onClick={handleClick}>Click me</button>
|
||||||
|
* })
|
||||||
|
* ```
|
||||||
*/
|
*/
|
||||||
export const useRunPromise = <R = never>(): Effect.Effect<
|
export const useRunPromise = <R = never>(): Effect.Effect<
|
||||||
<A, E = never>(effect: Effect.Effect<A, E, Scope.Scope | R>) => Promise<A>,
|
<A, E = never>(effect: Effect.Effect<A, E, Scope.Scope | R>) => Promise<A>,
|
||||||
@@ -693,7 +911,31 @@ export const useRunPromise = <R = never>(): Effect.Effect<
|
|||||||
> => Effect.andThen(Effect.runtime(), context => Runtime.runPromise(context))
|
> => Effect.andThen(Effect.runtime(), context => Runtime.runPromise(context))
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Turns a function returning an effect into a memoized synchronous function.
|
* 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* useCallbackSync(
|
||||||
|
* (data: Data) => saveData(data),
|
||||||
|
* [props.onSave]
|
||||||
|
* )
|
||||||
|
* return <button onClick={() => handleSave(myData)}>Save</button>
|
||||||
|
* })
|
||||||
|
* ```
|
||||||
*/
|
*/
|
||||||
export const useCallbackSync = Effect.fnUntraced(function* <Args extends unknown[], A, E, R>(
|
export const useCallbackSync = Effect.fnUntraced(function* <Args extends unknown[], A, E, R>(
|
||||||
f: (...args: Args) => Effect.Effect<A, E, R>,
|
f: (...args: Args) => Effect.Effect<A, E, R>,
|
||||||
@@ -708,7 +950,41 @@ export const useCallbackSync = Effect.fnUntraced(function* <Args extends unknown
|
|||||||
})
|
})
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Turns a function returning an effect into a memoized Promise-based asynchronous function.
|
* 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* useCallbackPromise(
|
||||||
|
* (data: Data) => saveData(data),
|
||||||
|
* [props.onSave]
|
||||||
|
* )
|
||||||
|
* return (
|
||||||
|
* <button onClick={async () => {
|
||||||
|
* try {
|
||||||
|
* await handleSave(myData)
|
||||||
|
* } catch (error) {
|
||||||
|
* console.error(error)
|
||||||
|
* }
|
||||||
|
* }}>
|
||||||
|
* Save
|
||||||
|
* </button>
|
||||||
|
* )
|
||||||
|
* })
|
||||||
|
* ```
|
||||||
*/
|
*/
|
||||||
export const useCallbackPromise = Effect.fnUntraced(function* <Args extends unknown[], A, E, R>(
|
export const useCallbackPromise = Effect.fnUntraced(function* <Args extends unknown[], A, E, R>(
|
||||||
f: (...args: Args) => Effect.Effect<A, E, R>,
|
f: (...args: Args) => Effect.Effect<A, E, R>,
|
||||||
@@ -727,11 +1003,48 @@ export declare namespace useContext {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Hook that constructs a layer and returns the created context.
|
* Effect hook that constructs an Effect Layer and returns the resulting context.
|
||||||
*
|
*
|
||||||
* The layer gets reconstructed everytime `layer` changes, so make sure its value is stable.
|
* 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).
|
||||||
*
|
*
|
||||||
* Building a layer containing asynchronous effects require the component calling this hook to be made async using `Async.async`.
|
* 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 MyComponent = Component.make(function* (props) {
|
||||||
|
* const context = yield* useContext(
|
||||||
|
* Layer.succeed(MyService, new MyServiceImpl())
|
||||||
|
* )
|
||||||
|
* // Use context to access services
|
||||||
|
* return <div>Using services</div>
|
||||||
|
* })
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* @example With async layer
|
||||||
|
* ```tsx
|
||||||
|
* const MyComponent = Component.make(function* (props) {
|
||||||
|
* const context = yield* useContext(
|
||||||
|
* Layer.effect(MyService, () => fetchServiceConfig())
|
||||||
|
* )
|
||||||
|
* return <div>Using async services</div>
|
||||||
|
* })
|
||||||
|
*
|
||||||
|
* // Must be wrapped with Async.async
|
||||||
|
* export default Async.async(MyComponent)
|
||||||
|
* ```
|
||||||
*/
|
*/
|
||||||
export const useContext = <ROut, E, RIn>(
|
export const useContext = <ROut, E, RIn>(
|
||||||
layer: Layer.Layer<ROut, E, RIn>,
|
layer: Layer.Layer<ROut, E, RIn>,
|
||||||
|
|||||||
Reference in New Issue
Block a user