From 2c78b17f529723b0c2d81cb9dd8827629a065785 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Valverd=C3=A9?= Date: Fri, 27 Feb 2026 10:16:52 +0100 Subject: [PATCH] Update comments --- packages/effect-fc/src/Component.ts | 198 ++++++++++++++++------------ 1 file changed, 111 insertions(+), 87 deletions(-) diff --git a/packages/effect-fc/src/Component.ts b/packages/effect-fc/src/Component.ts index 19e9726..28f0091 100644 --- a/packages/effect-fc/src/Component.ts +++ b/packages/effect-fc/src/Component.ts @@ -365,7 +365,7 @@ export declare namespace make { * - **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: + * 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 * @@ -382,14 +382,23 @@ export declare namespace make { * class MyComponent extends Component.make("MyComponent")(function* (props: { count: number }) { * const value = yield* someEffect * return
{value}
- * }) + * }) {} * ``` * * @example Without name * ```tsx - * const MyComponent = Component.make((props: { count: number }) => { - * return Effect.sync(() =>
{props.count}
) - * }) + * 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: ( @@ -431,22 +440,39 @@ export const make: ( * - 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 + * 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(function* (props: { count: number }) { + * const MyComponent = Component.makeUntraced("MyComponent")(function* (props: { count: number }) { * const value = yield* someEffect * return
{value}
* }) * ``` * - * @example With displayName only + * @example As an opaque type using class syntax * ```tsx - * const MyComponent = Component.makeUntraced("MyComponent", (props: { count: number }) => { - * return Effect.sync(() =>
{props.count}
) - * }) + * 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: ( @@ -478,7 +504,6 @@ export const makeUntraced: ( * * @example * ```tsx - * const MyComponent = Component.make(...) * const MyComponentWithCustomOptions = MyComponent.pipe( * Component.withOptions({ * finalizerExecutionStrategy: ExecutionStrategy.parallel, @@ -507,17 +532,14 @@ export const withOptions: { * 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 the recommended approach for integrating Effect-FC components with the broader React ecosystem, + * 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, enabling: - * - Single-point dependency injection at the application root - * - Consistent runtime environment across entire route trees or feature modules - * - Proper resource lifecycle management across component hierarchies + * 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. @@ -669,8 +691,8 @@ export const useScope = Effect.fnUntraced(function*( * * @example * ```tsx - * const MyComponent = Component.make(function* (props) { - * const initialData = yield* useOnMount(() => fetchData) + * const MyComponent = Component.make(function*() { + * const initialData = yield* Component.useOnMount(() => getData) * return
{initialData}
* }) * ``` @@ -707,8 +729,8 @@ export declare namespace useOnChange { * @example * ```tsx * const MyComponent = Component.make(function* (props: { userId: string }) { - * const userData = yield* useOnChange( - * fetchUser(props.userId), + * const userData = yield* Component.useOnChange( + * getUser(props.userId), * [props.userId], * ) * return
{userData.name}
@@ -740,7 +762,7 @@ export declare namespace useReactEffect { * 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 side effects while maintaining React's dependency tracking and lifecycle semantics. + * 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 @@ -756,14 +778,15 @@ export declare namespace useReactEffect { * @example * ```tsx * const MyComponent = Component.make(function* (props: { id: string }) { - * yield* useReactEffect( - * () => { - * const subscription = subscribe(props.id) - * return Effect.addFinalizer(() => subscription.unsubscribe()) - * }, - * [props.id] + * yield* Component.useReactEffect( + * () => getNotificationStreamForUser(props.id).pipe( + * Stream.unwrap, + * Stream.runForEach(notification => Console.log(`Notification received: ${ notification }`), + * Effect.forkScoped, + * ), + * [props.id], * ) - * return
Subscribed to {props.id}
+ * return
Subscribed to notifications for {props.id}
* }) * ``` */ @@ -826,17 +849,17 @@ export declare namespace useReactLayoutEffect { * * @example * ```tsx - * const MyComponent = Component.make(function* (props) { + * const MyComponent = Component.make(function*() { * const ref = React.useRef(null) - * yield* useReactLayoutEffect( - * () => { - * if (ref.current) { - * const height = ref.current.offsetHeight - * // Perform layout-dependent operations + * 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 Effect.void - * }, - * [] + * }), + * [], * ) * return
Content
* }) @@ -863,13 +886,11 @@ export const useReactLayoutEffect = Effect.fnUntraced(function* ( * * @example * ```tsx - * const MyComponent = Component.make(function* (props) { - * const runSync = yield* useRunSync() - * const handleClick = () => { - * const result = runSync(someEffect) - * console.log(result) - * } - * return + * const MyComponent = Component.make(function*() { + * const runSync = yield* Component.useRunSync() // Specify required services + * const runSync = yield* Component.useRunSync() // Or no service requirements + * + * return * }) * ``` */ @@ -890,17 +911,11 @@ export const useRunSync = (): Effect.Effect< * * @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 + * const MyComponent = Component.make(function*() { + * const runPromise = yield* Component.useRunPromise() // Specify required services + * const runPromise = yield* Component.useRunPromise() // Or no service requirements + * + * return * }) * ``` */ @@ -929,10 +944,11 @@ export const useRunPromise = (): Effect.Effect< * @example * ```tsx * const MyComponent = Component.make(function* (props: { onSave: (data: Data) => void }) { - * const handleSave = yield* useCallbackSync( - * (data: Data) => saveData(data), - * [props.onSave] + * const handleSave = yield* Component.useCallbackSync( + * (data: Data) => Effect.sync(() => props.onSave(data)), + * [props.onSave], * ) + * * return * }) * ``` @@ -968,21 +984,12 @@ export const useCallbackSync = Effect.fnUntraced(function* void }) { - * const handleSave = yield* useCallbackPromise( - * (data: Data) => saveData(data), - * [props.onSave] - * ) - * return ( - * + * const handleSave = yield* Component.useCallbackPromise( + * (data: Data) => Effect.promise(() => props.onSave(data)), + * [props.onSave], * ) + * + * return * }) * ``` */ @@ -1024,32 +1031,49 @@ export declare namespace useContext { * * @example * ```tsx - * const MyComponent = Component.make(function* (props) { - * const context = yield* useContext( - * Layer.succeed(MyService, new MyServiceImpl()) + * const MyLayer = Layer.succeed(MyService, new MyServiceImpl()) + * const MyComponent = Component.make(function*() { + * const context = yield* Component.useContext(MyLayer) + * const Sub = yield* SubComponent.use.pipe( + * Effect.provide(context) * ) - * // Use context to access services - * return
Using services
+ * + * return + * }) + * ``` + * + * @example With memoized layer + * ```tsx + * const MyComponent = Component.make(function*(props: { id: string })) { + * const context = yield* Component.useContext( + * 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 MyComponent = Component.make(function* (props) { - * const context = yield* useContext( - * Layer.effect(MyService, () => fetchServiceConfig()) + * const MyAsyncLayer = Layer.effect(MyService, someAsyncEffect) + * const MyComponent = Component.make(function*() { + * const context = yield* Component.useContext(MyAsyncLayer) + * const Sub = yield* SubComponent.use.pipe( + * Effect.provide(context) * ) - * return
Using async services
- * }) * - * // Must be wrapped with Async.async - * export default Async.async(MyComponent) - * ``` + * return + * }).pipe( + * Async.async // Required to handle async layer effects + * ) */ export const useContext = ( layer: Layer.Layer, options?: useContext.Options, -): Effect.Effect, E, Exclude> => useOnChange(() => Effect.context().pipe( +): 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),