diff --git a/bun.lockb b/bun.lockb index 2cfc905..5e9fd1e 100755 Binary files a/bun.lockb and b/bun.lockb differ diff --git a/packages/example/package.json b/packages/example/package.json index d73afec..440093d 100644 --- a/packages/example/package.json +++ b/packages/example/package.json @@ -35,6 +35,7 @@ "@effect/platform-browser": "^0.52.1", "@radix-ui/themes": "^3.1.6", "@typed/id": "^0.17.1", + "@typed/lazy-ref": "^0.3.3", "lucide-react": "^0.471.1", "mobx": "^6.13.5" } diff --git a/packages/example/src/routes/tests.tsx b/packages/example/src/routes/tests.tsx index 5528315..a019c4b 100644 --- a/packages/example/src/routes/tests.tsx +++ b/packages/example/src/routes/tests.tsx @@ -1,5 +1,6 @@ import { R } from "@/reffuse" import { createFileRoute } from "@tanstack/react-router" +import { GetRandomValues, makeUuid4 } from "@typed/id" import { Console, Effect } from "effect" @@ -12,13 +13,11 @@ function RouteComponent() { // Effect.map(() => "test") // )) - R.useSuspenseScoped(Effect.addFinalizer(() => Console.log("cleanup")).pipe( - Effect.andThen(Effect.promise(() => new Promise(resolve => { - setTimeout(() => { resolve("test") }, 0) - }))), - - Effect.tap(Console.log), + const value = R.useMemoScoped(Effect.addFinalizer(() => Console.log("cleanup")).pipe( + Effect.andThen(makeUuid4), + Effect.provide(GetRandomValues.CryptoRandom), ), []) + console.log(value) return
Hello "/tests"!
} diff --git a/packages/reffuse/src/Reffuse.ts b/packages/reffuse/src/Reffuse.ts index 389601f..aea43a7 100644 --- a/packages/reffuse/src/Reffuse.ts +++ b/packages/reffuse/src/Reffuse.ts @@ -99,13 +99,39 @@ export class Reffuse { ): A { const runSync = this.useRunSync() - const initialValue = React.useMemo(() => runSync(Effect.scoped(effect)), []) + // Calculate an initial version of the value so that it can be accessed during the first render + const [initialScope, initialValue] = React.useMemo(() => Scope.make(options?.finalizerExecutionStrategy).pipe( + Effect.flatMap(scope => effect.pipe( + Effect.provideService(Scope.Scope, scope), + Effect.map(value => [scope, value] as const), + )), + + runSync, + ), []) + + // Keep track of the state of the initial scope + const initialScopeClosed = React.useRef(false) + const [value, setValue] = React.useState(initialValue) React.useEffect(() => { - const scope = runSync(Scope.make(options?.finalizerExecutionStrategy)) - setValue(runSync(Effect.provideService(effect, Scope.Scope, scope))) + const closeInitialScope = Scope.close(initialScope, Exit.void).pipe( + Effect.andThen(Effect.sync(() => { initialScopeClosed.current = true })), + Effect.when(() => !initialScopeClosed.current), + ) + const [scope, value] = closeInitialScope.pipe( + Effect.andThen(Scope.make(options?.finalizerExecutionStrategy).pipe( + Effect.flatMap(scope => effect.pipe( + Effect.provideService(Scope.Scope, scope), + Effect.map(value => [scope, value] as const), + )) + )), + + runSync, + ) + + setValue(value) return () => { runSync(Scope.close(scope, Exit.void)) } }, [ ...options?.doNotReExecuteOnRuntimeOrContextChange ? [] : [runSync],