From 8b948b2251b38a5b1a3018fc3b88402cd2fe2903 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Valverd=C3=A9?= Date: Wed, 29 Oct 2025 16:52:56 +0100 Subject: [PATCH] useOnChangeResult --- packages/effect-fc/src/Component.ts | 37 ++++++++++++++++++++++++++++- packages/effect-fc/src/Result.ts | 2 +- 2 files changed, 37 insertions(+), 2 deletions(-) diff --git a/packages/effect-fc/src/Component.ts b/packages/effect-fc/src/Component.ts index 6ca3adc..c2823b8 100644 --- a/packages/effect-fc/src/Component.ts +++ b/packages/effect-fc/src/Component.ts @@ -1,8 +1,9 @@ /** biome-ignore-all lint/complexity/noBannedTypes: {} is the default type for React props */ /** biome-ignore-all lint/complexity/useArrowFunction: necessary for class prototypes */ -import { Context, type Duration, Effect, Effectable, Equivalence, ExecutionStrategy, Exit, Fiber, Function, HashMap, Layer, ManagedRuntime, Option, Predicate, Ref, Runtime, Scope, Tracer, type Types, type Utils } from "effect" +import { Context, type Duration, Effect, Effectable, Equivalence, ExecutionStrategy, Exit, Fiber, Function, HashMap, Layer, ManagedRuntime, Option, Predicate, Ref, Runtime, Scope, Stream, Tracer, type Types, type Utils } from "effect" import * as React from "react" import { Memoized } from "./index.js" +import * as Result from "./Result.js" export const TypeId: unique symbol = Symbol.for("@effect-fc/Component/Component") @@ -513,6 +514,40 @@ export const useOnChange: { ), [scope]) }) +export namespace useOnChangeResult { + export interface Options + extends Result.forkEffectScoped.Options

, useReactEffect.Options { + readonly equivalence?: Equivalence.Equivalence> + } +} + +export const useOnChangeResult: { + ( + f: () => Effect.Effect>>, + deps?: React.DependencyList, + options?: useOnChangeResult.Options, + ): Effect.Effect< + Result.Result, + never, + Exclude, Scope.Scope> + > +} = Effect.fnUntraced(function* ( + f: () => Effect.Effect>>, + deps?: React.DependencyList, + options?: useOnChangeResult.Options, +) { + const [result, setResult] = React.useState(() => Result.initial() as Result.Result) + + yield* useReactEffect(() => Result.forkEffectScoped(f(), options).pipe( + Effect.andThen(Stream.fromQueue), + Stream.unwrap, + Stream.changesWith(options?.equivalence ?? Equivalence.strict()), + Stream.runForEach(result => Effect.sync(() => setResult(result))), + ), deps, options) + + return result +}) + export namespace useReactEffect { export interface Options { readonly finalizerExecutionMode?: "sync" | "fork" diff --git a/packages/effect-fc/src/Result.ts b/packages/effect-fc/src/Result.ts index c9bddc9..f3bd269 100644 --- a/packages/effect-fc/src/Result.ts +++ b/packages/effect-fc/src/Result.ts @@ -185,7 +185,7 @@ export namespace forkEffectScoped { : R ) - export interface Options

{ + export interface Options

{ readonly initialProgress?: P }