diff --git a/packages/effect-fc/src/Component.ts b/packages/effect-fc/src/Component.ts index fbb6833..1ebd387 100644 --- a/packages/effect-fc/src/Component.ts +++ b/packages/effect-fc/src/Component.ts @@ -1,4 +1,4 @@ -import { Context, Effect, ExecutionStrategy, Function, Pipeable, Predicate, Runtime, Scope, String, Tracer, type Utils } from "effect" +import { Context, Effect, Effectable, ExecutionStrategy, Function, Predicate, Runtime, Scope, String, Tracer, type Utils } from "effect" import * as React from "react" import * as Hook from "./Hook.js" import * as Memoized from "./Memoized.js" @@ -7,7 +7,7 @@ import * as Memoized from "./Memoized.js" export const TypeId: unique symbol = Symbol.for("effect-fc/Component") export type TypeId = typeof TypeId -export interface Component extends Pipeable.Pipeable, Component.Options { +export interface Component extends Effect.Effect, never, R>, Component.Options { new(_: never): {} readonly [TypeId]: TypeId /** @internal */ @@ -30,8 +30,34 @@ export namespace Component { const ComponentProto = Object.freeze({ + ...Effectable.CommitPrototype, [TypeId]: TypeId, - pipe() { return Pipeable.pipeArguments(this, arguments) }, + + commit: Effect.fnUntraced(function* (this: Component) { + const self = this + + const runtimeRef = React.useRef>>(null!) + runtimeRef.current = yield* Effect.runtime>() + + return React.useCallback(function ScopeProvider(props: P) { + const scope = Runtime.runSync(runtimeRef.current)(Hook.useScope( + Array.from( + Context.omit(...nonReactiveTags)(runtimeRef.current.context).unsafeMap.values() + ), + self, + )) + + const FC = React.useMemo(() => { + const f = self.makeFunctionComponent(runtimeRef, scope) + f.displayName = self.displayName ?? "Anonymous" + return Memoized.isMemoized(self) + ? React.memo(f, self.propsAreEqual) + : f + }, [scope]) + + return React.createElement(FC, props) + }, []) + }), makeFunctionComponent( this: Component, @@ -52,7 +78,7 @@ const defaultOptions = { const nonReactiveTags = [Tracer.ParentSpan] as const -export const isComponent = (u: unknown): u is Component => Predicate.hasProperty(u, TypeId) +export const isComponent = (u: unknown): u is Component => Predicate.hasProperty(u, TypeId) export namespace make { export type Gen = { diff --git a/packages/effect-fc/src/Memoized.ts b/packages/effect-fc/src/Memoized.ts index 09b476e..6358351 100644 --- a/packages/effect-fc/src/Memoized.ts +++ b/packages/effect-fc/src/Memoized.ts @@ -21,7 +21,7 @@ const MemoizedProto = Object.freeze({ } as const) -export const isMemoized = (u: unknown): u is Memoized => Predicate.hasProperty(u, TypeId) +export const isMemoized = (u: unknown): u is Memoized => Predicate.hasProperty(u, TypeId) export const memo = >( self: T diff --git a/packages/example/src/routes/dev/async-rendering.tsx b/packages/example/src/routes/dev/async-rendering.tsx index ecc52e6..aff109e 100644 --- a/packages/example/src/routes/dev/async-rendering.tsx +++ b/packages/example/src/routes/dev/async-rendering.tsx @@ -9,8 +9,8 @@ import * as React from "react" // Generator version const RouteComponent = Component.make(function* AsyncRendering() { - const VMemoizedAsyncComponent = yield* Component.useFC(MemoizedAsyncComponent) - const VAsyncComponent = yield* Component.useFC(AsyncComponent) + const VMemoizedAsyncComponent = yield* MemoizedAsyncComponent + const VAsyncComponent = yield* AsyncComponent const [input, setInput] = React.useState("") return (