From 19194d6677583469995fbdaf315c91c19ec1c826 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Valverd=C3=A9?= Date: Tue, 1 Jul 2025 18:13:03 +0200 Subject: [PATCH] Hook --- packages/effect-components/src/ReactHook.ts | 61 ++++++++++++++++++++- 1 file changed, 60 insertions(+), 1 deletion(-) diff --git a/packages/effect-components/src/ReactHook.ts b/packages/effect-components/src/ReactHook.ts index c2cb50b..245424a 100644 --- a/packages/effect-components/src/ReactHook.ts +++ b/packages/effect-components/src/ReactHook.ts @@ -1,4 +1,4 @@ -import { type Context, Effect, ExecutionStrategy, Exit, type Layer, pipe, Ref, Runtime, Scope, Stream, type SubscriptionRef } from "effect" +import { type Context, Effect, ExecutionStrategy, Exit, type Layer, Option, pipe, PubSub, Ref, Runtime, Scope, Stream, SubscriptionRef } from "effect" import * as React from "react" import { SetStateAction } from "./types/index.js" @@ -212,6 +212,14 @@ export const useFork: { }) +export const useRefFromReactiveValue: { + (value: A): Effect.Effect> +} = Effect.fnUntraced(function*(value) { + const ref = yield* useOnce(() => SubscriptionRef.make(value)) + yield* useEffect(() => Ref.set(ref, value), [value]) + return ref +}) + export const useSubscribeRefs: { []>( ...refs: Refs @@ -254,3 +262,54 @@ export const useRefState: { return [reactStateValue, setValue] }) + + +export const useStreamFromReactiveValues: { + ( + values: A + ): Effect.Effect, never, Scope.Scope> +} = Effect.fnUntraced(function* (values: A) { + const { latest, pubsub, stream } = yield* useOnce(() => Effect.Do.pipe( + Effect.bind("latest", () => Ref.make(values)), + Effect.bind("pubsub", () => Effect.acquireRelease(PubSub.unbounded(), PubSub.shutdown)), + Effect.let("stream", ({ latest, pubsub }) => latest.pipe( + Effect.flatMap(a => Effect.map( + Stream.fromPubSub(pubsub, { scoped: true }), + s => Stream.concat(Stream.make(a), s), + )), + Stream.unwrapScoped, + )), + )) + + yield* useEffect(() => Ref.set(latest, values).pipe( + Effect.andThen(PubSub.publish(pubsub, values)), + Effect.unlessEffect(PubSub.isShutdown(pubsub)), + ), values) + + return stream +}) + +export const useSubscribeStream: { + (stream: Stream.Stream): Effect.Effect, never, R> + ( + stream: Stream.Stream, + initialValue: A, + ): Effect.Effect, never, R> +} = Effect.fnUntraced(function* ( + stream: Stream.Stream, + initialValue?: A, +) { + const [reactStateValue, setReactStateValue] = React.useState>( + React.useMemo(() => initialValue + ? Option.some(initialValue) + : Option.none(), + []) + ) + + yield* useFork(() => Stream.runForEach( + Stream.changesWith(stream, (x, y) => x === y), + v => Effect.sync(() => setReactStateValue(Option.some(v))), + ), [stream]) + + return reactStateValue +})