diff --git a/packages/effect-components/src/ReactComponent.ts b/packages/effect-components/src/ReactComponent.ts
index e20f4a0..d16c802 100644
--- a/packages/effect-components/src/ReactComponent.ts
+++ b/packages/effect-components/src/ReactComponent.ts
@@ -1,23 +1,30 @@
-import { Context, Effect, Runtime, Tracer } from "effect"
+import { Context, Effect, Function, Runtime, Tracer } from "effect"
+import type { Mutable } from "effect/Types"
import * as React from "react"
-import * as ReactHook from "./ReactHook.js"
-export interface ReactComponent
{
+export interface ReactComponent {
(props: P): Effect.Effect
readonly displayName?: string
}
export const nonReactiveTags = [Tracer.ParentSpan] as const
+export const withDisplayName: {
+ >(displayName: string): (self: C) => C
+ >(self: C, displayName: string): C
+} = Function.dual(2, >(
+ self: C,
+ displayName: string,
+): C => {
+ (self as Mutable).displayName = displayName
+ return self
+})
export const useFC: {
- (
- self: ReactComponent
,
- options?: ReactHook.ScopeOptions,
- ): Effect.Effect, never, R>
-} = Effect.fnUntraced(function* useFC(
- self: ReactComponent
+ (self: ReactComponent): Effect.Effect, never, R>
+} = Effect.fnUntraced(function* (
+ self: ReactComponent
) {
const runtime = yield* Effect.runtime()
@@ -31,14 +38,24 @@ export const useFC: {
})
export const use: {
- (
- self: ReactComponent
,
+ (
+ self: ReactComponent,
fn: (Component: React.FC) => React.ReactNode,
): Effect.Effect
-} = Effect.fnUntraced(function* use(self, fn) {
+} = Effect.fnUntraced(function*(self, fn) {
return fn(yield* useFC(self))
})
+export const withRuntime: {
+ (context: React.Context>): (self: ReactComponent) => React.FC
+ (self: ReactComponent, context: React.Context>): React.FC
+} = Function.dual(2, (
+ self: ReactComponent,
+ context: React.Context>,
+): React.FC => function WithRuntime(props) {
+ const runtime = React.useContext(context)
+ return React.createElement(Runtime.runSync(runtime)(useFC(self)), props)
+})
// export const useFC: {
//
(
diff --git a/packages/effect-components/src/ReactHook.ts b/packages/effect-components/src/ReactHook.ts
index 960bc1f..7362ad0 100644
--- a/packages/effect-components/src/ReactHook.ts
+++ b/packages/effect-components/src/ReactHook.ts
@@ -13,7 +13,7 @@ export const useMemo: {
factory: () => Effect.Effect,
deps: React.DependencyList,
): Effect.Effect
-} = Effect.fnUntraced(function* useMemo(
+} = Effect.fnUntraced(function* (
factory: () => Effect.Effect,
deps: React.DependencyList,
) {
@@ -23,7 +23,7 @@ export const useMemo: {
export const useOnce: {
(factory: () => Effect.Effect): Effect.Effect
-} = Effect.fnUntraced(function* useOnce(
+} = Effect.fnUntraced(function* (
factory: () => Effect.Effect
) {
return yield* useMemo(factory, [])
@@ -31,16 +31,16 @@ export const useOnce: {
export const useEffect: {
(
- effect: () => Effect.Effect,
+ effect: () => Effect.Effect,
deps?: React.DependencyList,
options?: ScopeOptions,
- ): Effect.Effect
-} = Effect.fnUntraced(function* useEffect(
- effect: () => Effect.Effect,
+ ): Effect.Effect>
+} = Effect.fnUntraced(function* (
+ effect: () => Effect.Effect,
deps?: React.DependencyList,
options?: ScopeOptions,
) {
- const runtime = yield* Effect.runtime()
+ const runtime = yield* Effect.runtime>()
React.useEffect(() => {
const { scope, exit } = Effect.Do.pipe(
@@ -64,16 +64,16 @@ export const useEffect: {
export const useLayoutEffect: {
(
- effect: () => Effect.Effect,
+ effect: () => Effect.Effect,
deps?: React.DependencyList,
options?: ScopeOptions,
- ): Effect.Effect
-} = Effect.fnUntraced(function* useLayoutEffect(
- effect: () => Effect.Effect,
+ ): Effect.Effect>
+} = Effect.fnUntraced(function* (
+ effect: () => Effect.Effect,
deps?: React.DependencyList,
options?: ScopeOptions,
) {
- const runtime = yield* Effect.runtime()
+ const runtime = yield* Effect.runtime>()
React.useLayoutEffect(() => {
const { scope, exit } = Effect.Do.pipe(
diff --git a/packages/effect-components/src/ReactManagedRuntime.ts b/packages/effect-components/src/ReactManagedRuntime.ts
new file mode 100644
index 0000000..d964e94
--- /dev/null
+++ b/packages/effect-components/src/ReactManagedRuntime.ts
@@ -0,0 +1,29 @@
+import { Effect, type Layer, ManagedRuntime, Runtime } from "effect"
+import * as React from "react"
+
+
+export interface ReactManagedRuntime {
+ readonly runtime: ManagedRuntime.ManagedRuntime
+ readonly context: React.Context>
+}
+
+export const make = (
+ layer: Layer.Layer,
+ memoMap?: Layer.MemoMap,
+): ReactManagedRuntime => ({
+ runtime: ManagedRuntime.make(layer, memoMap),
+ context: React.createContext>(null!),
+})
+
+export interface SyncProviderProps {
+ readonly runtime: ReactManagedRuntime
+ readonly children?: React.ReactNode
+}
+
+export const SyncProvider = (
+ props: SyncProviderProps
+): React.ReactNode => React.createElement(props.runtime.context, {
+ value: React.useMemo(() => Effect.runSync(props.runtime.runtime.runtimeEffect), [props.runtime]),
+ children: props.children,
+})
+SyncProvider.displayName = "ReactManagedRuntimeSyncProvider"
diff --git a/packages/effect-components/src/index.ts b/packages/effect-components/src/index.ts
index 3db5a6c..8b8a4b1 100644
--- a/packages/effect-components/src/index.ts
+++ b/packages/effect-components/src/index.ts
@@ -1,2 +1,3 @@
export * as ReactComponent from "./ReactComponent.js"
export * as ReactHook from "./ReactHook.js"
+export * as ReactManagedRuntime from "./ReactManagedRuntime.js"
diff --git a/packages/example/src/routes/effect-component-tests.tsx b/packages/example/src/routes/effect-component-tests.tsx
index 8eb1c22..3dadb32 100644
--- a/packages/example/src/routes/effect-component-tests.tsx
+++ b/packages/example/src/routes/effect-component-tests.tsx
@@ -1,44 +1,56 @@
-import { Box, Text, TextField } from "@radix-ui/themes"
+import { Box, TextField } from "@radix-ui/themes"
import { createFileRoute } from "@tanstack/react-router"
-import { Console, Effect, Layer, ManagedRuntime, SubscriptionRef } from "effect"
-import { ReactComponent, ReactHook } from "effect-components"
+import { Console, Effect, Layer, pipe, SubscriptionRef } from "effect"
+import { ReactComponent, ReactHook, ReactManagedRuntime } from "effect-components"
import * as React from "react"
+class TestService extends Effect.Service()("TestService", {
+ effect: Effect.bind(Effect.Do, "ref", () => SubscriptionRef.make("value")),
+}) {}
+
+const runtime = ReactManagedRuntime.make(Layer.empty)
+
+
export const Route = createFileRoute("/effect-component-tests")({
component: RouteComponent,
})
function RouteComponent() {
- const runtime = React.useMemo(() => ManagedRuntime.make(Layer.empty), [])
-
- return <>
- {runtime.runSync(ReactComponent.use(MyTestComponent, Component => (
-
- )))}
- >
+ return (
+
+
+
+ )
}
+const MyRoute = pipe(
+ Effect.fn(function*() {
+ return yield* ReactComponent.use(MyTestComponent, C => )
+ }),
+ ReactComponent.withDisplayName("MyRoute"),
+ ReactComponent.withRuntime(runtime.context),
+)
-class TestService extends Effect.Service()("TestService", {
- effect: Effect.bind(Effect.Do, "ref", () => SubscriptionRef.make("value")),
-}) {}
-const MyTestComponent = Effect.fn(function* MyTestComponent(props?: { readonly value?: string }) {
- const [state, setState] = React.useState("value")
+const MyTestComponent = pipe(
+ Effect.fn(function*() {
+ const [state, setState] = React.useState("value")
- // yield* ReactHook.useEffect(() => Effect.andThen(
- // Effect.addFinalizer(() => Console.log("MyTestComponent umounted")),
- // Console.log("MyTestComponent mounted"),
- // ), [])
+ yield* ReactHook.useEffect(() => Effect.andThen(
+ Effect.addFinalizer(() => Console.log("MyTestComponent umounted")),
+ Console.log("MyTestComponent mounted"),
+ ), [])
- return <>
-
- setState(e.target.value)}
- />
-
- >
-})
-console.log(MyTestComponent)
+ return <>
+
+ setState(e.target.value)}
+ />
+
+ >
+ }),
+
+ ReactComponent.withDisplayName("MyTestComponent"),
+)