diff --git a/packages/example/src/routes/promise.tsx b/packages/example/src/routes/promise.tsx
index 9d69d62..da200db 100644
--- a/packages/example/src/routes/promise.tsx
+++ b/packages/example/src/routes/promise.tsx
@@ -2,44 +2,36 @@ import { R } from "@/reffuse"
import { HttpClient } from "@effect/platform"
import { Text } from "@radix-ui/themes"
import { createFileRoute } from "@tanstack/react-router"
-import { Console, Effect } from "effect"
-import { Suspense, use, useEffect, useMemo } from "react"
+import { Effect, Schema } from "effect"
+import { Suspense, use } from "react"
export const Route = createFileRoute("/promise")({
component: RouteComponent
})
+
+const Result = Schema.Tuple(Schema.String)
+type Result = typeof Result.Type
+
function RouteComponent() {
+ const promise = R.usePromise(HttpClient.HttpClient.pipe(
+ Effect.flatMap(client => client.get("https://www.uuidtools.com/api/generate/v4")),
+ HttpClient.withTracerPropagation(false),
+ Effect.flatMap(res => res.json),
+ Effect.flatMap(Schema.decodeUnknown(Result)),
+
+ Effect.scoped,
+ ))
+
return (
Loading...}>
-
+
)
}
-function AsyncComponent() {
-
- // const runPromise = R.useRunPromise()
-
- // const promise = useMemo(() => HttpClient.HttpClient.pipe(
- // Effect.flatMap(client => client.get("https://www.uuidtools.com/api/generate/v4")),
- // HttpClient.withTracerPropagation(false),
- // Effect.flatMap(res => res.json),
- // Effect.tap(Console.log),
-
- // Effect.scoped,
- // runPromise,
- // ), [runPromise])
-
- const promise = useMemo(() => new Promise((resolve => {
- setTimeout(() => { resolve("prout") }, 500)
- })), [])
-
- console.log("React.use invoked with:", promise);
- const value = use(promise)
-
-
- return Hello "/tests"!
-
+function AsyncComponent({ promise }: { readonly promise: Promise }) {
+ const [uuid] = use(promise)
+ return {uuid}
}
diff --git a/packages/reffuse/src/Reffuse.ts b/packages/reffuse/src/Reffuse.ts
index 4ec7386..cc1755f 100644
--- a/packages/reffuse/src/Reffuse.ts
+++ b/packages/reffuse/src/Reffuse.ts
@@ -284,43 +284,57 @@ export class Reffuse {
])
}
- // useSuspense(
- // effect: Effect.Effect,
- // deps?: React.DependencyList,
- // options?: { readonly signal?: AbortSignal } & RenderOptions,
- // ): A {
- // const runPromise = this.useRunPromise()
+ usePromise(
+ effect: Effect.Effect,
+ deps?: React.DependencyList,
+ options?: { readonly signal?: AbortSignal } & RenderOptions,
+ ): Promise {
+ const runPromise = this.useRunPromise()
- // const promise = React.useMemo(() => runPromise(effect, options), [
- // ...options?.doNotReExecuteOnRuntimeOrContextChange ? [] : [runPromise],
- // ...(deps ?? []),
- // ])
- // return React.use(promise)
- // }
+ return React.useMemo(() => runPromise(effect, options), [
+ ...options?.doNotReExecuteOnRuntimeOrContextChange ? [] : [runPromise],
+ ...(deps ?? []),
+ ])
+ }
- // useSuspenseScoped(
- // effect: Effect.Effect,
- // deps?: React.DependencyList,
- // options?: { readonly signal?: AbortSignal } & RenderOptions & ScopeOptions,
- // ): A {
- // const runSync = this.useRunSync()
- // const runPromise = this.useRunPromise()
+ usePromiseScoped(
+ effect: Effect.Effect,
+ deps?: React.DependencyList,
+ options?: { readonly signal?: AbortSignal } & RenderOptions & ScopeOptions,
+ ): Promise {
+ const runSync = this.useRunSync()
+ const runPromise = this.useRunPromise()
- // const initialPromise = React.useMemo(() => runPromise(Effect.scoped(effect)), [])
- // const [promise, setPromise] = React.useState(initialPromise)
+ // Calculate an initial version of the value so that it can be accessed during the first render
+ const initialScope = React.useMemo(() => runSync(Scope.make(options?.finalizerExecutionStrategy)), [])
+ const initialValue = React.useMemo(() => runPromise(Effect.provideService(effect, Scope.Scope, initialScope), options), [])
- // React.useEffect(() => {
- // const scope = runSync(Scope.make())
- // setPromise(runPromise(Effect.provideService(effect, Scope.Scope, scope), options))
+ // Keep track of the state of the initial scope
+ const initialScopeClosed = React.useRef(false)
- // return () => { runPromise(Scope.close(scope, Exit.void)) }
- // }, [
- // ...options?.doNotReExecuteOnRuntimeOrContextChange ? [] : [runSync, runPromise],
- // ...(deps ?? []),
- // ])
+ const [value, setValue] = React.useState(initialValue)
- // return React.use(promise)
- // }
+ React.useEffect(() => {
+ const closeInitialScopeIfNeeded = Scope.close(initialScope, Exit.void).pipe(
+ Effect.andThen(Effect.sync(() => { initialScopeClosed.current = true })),
+ Effect.when(() => !initialScopeClosed.current),
+ )
+
+ const scope = closeInitialScopeIfNeeded.pipe(
+ Effect.andThen(Scope.make(options?.finalizerExecutionStrategy)),
+ runSync,
+ )
+
+ setValue(runPromise(Effect.provideService(effect, Scope.Scope, initialScope), options))
+
+ return () => { runSync(Scope.close(scope, Exit.void)) }
+ }, [
+ ...options?.doNotReExecuteOnRuntimeOrContextChange ? [] : [runSync, runPromise],
+ ...(deps ?? []),
+ ])
+
+ return value
+ }
useRef(value: A): SubscriptionRef.SubscriptionRef {