diff --git a/packages/effect-fc-next/src/Component.ts b/packages/effect-fc-next/src/Component.ts index a651525..12002ad 100644 --- a/packages/effect-fc-next/src/Component.ts +++ b/packages/effect-fc-next/src/Component.ts @@ -1,6 +1,6 @@ /** 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, Equivalence, ExecutionStrategy, Exit, Fiber, Function, HashMap, identity, Layer, Option, Pipeable, Predicate, Ref, Runtime, Scope, Tracer } from "effect" +import { Context, type Duration, Effect, Equivalence, ExecutionStrategy, Exit, Fiber, Function, HashMap, identity, Layer, Option, Pipeable, Predicate, Ref, Scope, Tracer } from "effect" import * as React from "react" @@ -609,18 +609,18 @@ export const withOptions: { */ export const withRuntime: {

( - context: React.Context>, + context: React.Context>, ): (self: Component, F>) => F

( self: Component, F>, - context: React.Context>, + context: React.Context>, ): F } = Function.dual(2,

( self: Component, - context: React.Context>, + context: React.Context>, ) => function WithRuntime(props: P) { return React.createElement( - Runtime.runSync(React.useContext(context))(self.use) as React.FC

, + Effect.runSyncWith(React.useContext(context))(self.use) as React.FC

, props, ) }) diff --git a/packages/effect-fc-next/src/ReactRuntime.ts b/packages/effect-fc-next/src/ReactRuntime.ts index 92f9108..2c40a78 100644 --- a/packages/effect-fc-next/src/ReactRuntime.ts +++ b/packages/effect-fc-next/src/ReactRuntime.ts @@ -1,41 +1,52 @@ -/** biome-ignore-all lint/complexity/useArrowFunction: React component names are intentional */ -import { Context, Layer, ManagedRuntime, Predicate } from "effect" +/** biome-ignore-all lint/complexity/useArrowFunction: necessary for class prototypes */ +import { type Context, Effect, Layer, ManagedRuntime, Predicate } from "effect" import * as React from "react" +import * as Component from "./Component.js" import * as ErrorObserver from "./ErrorObserver.js" import * as QueryClient from "./QueryClient.js" -export const TypeId: unique symbol = Symbol.for("@effect-fc/ReactRuntime/ReactRuntime") -export type TypeId = typeof TypeId +export const ReactRuntimeTypeId: unique symbol = Symbol.for("@effect-fc/ReactRuntime/ReactRuntime") +export type ReactRuntimeTypeId = typeof ReactRuntimeTypeId export interface ReactRuntime { new(_: never): Record - readonly [TypeId]: TypeId + readonly [ReactRuntimeTypeId]: ReactRuntimeTypeId readonly runtime: ManagedRuntime.ManagedRuntime readonly context: React.Context> } -const ReactRuntimeProto = Object.freeze({ [TypeId]: TypeId } as const) +const ReactRuntimeProto = Object.freeze({ [ReactRuntimeTypeId]: ReactRuntimeTypeId } as const) -export const Prelude: Layer.Layer = Layer.merge( +export const preludeLayer: Layer.Layer< + | Component.ScopeMap + | ErrorObserver.ErrorObserver + | QueryClient.QueryClient +> = Layer.mergeAll( + Component.ScopeMap.layer, ErrorObserver.layer, QueryClient.QueryClient.Default, ) + export const isReactRuntime = (u: unknown): u is ReactRuntime => Predicate.hasProperty(u, TypeId) export const make = ( layer: Layer.Layer, memoMap?: Layer.MemoMap, -): ReactRuntime => Object.setPrototypeOf( +): ReactRuntime | R, ER> => Object.setPrototypeOf( Object.assign(function() {}, { - runtime: ManagedRuntime.make(Layer.merge(layer, Prelude), { memoMap }), - // biome-ignore lint/style/noNonNullAssertion: initialized by Provider before consumers render - context: React.createContext>(null!), + runtime: ManagedRuntime.make( + Layer.merge(layer, preludeLayer), + memoMap, + ), + // biome-ignore lint/style/noNonNullAssertion: context initialization + context: React.createContext>(null!), }), ReactRuntimeProto, ) + export namespace Provider { export interface Props extends React.SuspenseProps { readonly runtime: ReactRuntime @@ -44,8 +55,13 @@ export namespace Provider { } export const Provider = ( - { runtime, children, ...suspenseProps }: Provider.Props, + { runtime, children, ...suspenseProps }: Provider.Props ): React.ReactNode => { + Effect.runSync(Component.useOnChange( + () => Effect.addFinalizer(() => runtime.runtime.disposeEffect), + [runtime], + )) + const promise = React.useMemo(() => runtime.runtime.context(), [runtime]) return React.createElement( @@ -60,12 +76,5 @@ const ProviderInner = ( readonly runtime: ReactRuntime readonly promise: Promise> readonly children?: React.ReactNode - }, -): React.ReactNode => { - const context = React.use(promise) - React.useEffect(() => () => { - void runtime.runtime.dispose() - }, [runtime]) - - return React.createElement(runtime.context, { value: context }, children) -} + } +): React.ReactNode => React.createElement(runtime.context, { value: React.use(promise) }, children)