diff --git a/packages/example/src/routeTree.gen.ts b/packages/example/src/routeTree.gen.ts index 2cec328..92e0a99 100644 --- a/packages/example/src/routeTree.gen.ts +++ b/packages/example/src/routeTree.gen.ts @@ -12,7 +12,9 @@ import { Route as rootRoute } from './routes/__root' import { Route as TimeImport } from './routes/time' +import { Route as TestsImport } from './routes/tests' import { Route as CountImport } from './routes/count' +import { Route as BlankImport } from './routes/blank' import { Route as IndexImport } from './routes/index' // Create/Update Routes @@ -23,12 +25,24 @@ const TimeRoute = TimeImport.update({ getParentRoute: () => rootRoute, } as any) +const TestsRoute = TestsImport.update({ + id: '/tests', + path: '/tests', + getParentRoute: () => rootRoute, +} as any) + const CountRoute = CountImport.update({ id: '/count', path: '/count', getParentRoute: () => rootRoute, } as any) +const BlankRoute = BlankImport.update({ + id: '/blank', + path: '/blank', + getParentRoute: () => rootRoute, +} as any) + const IndexRoute = IndexImport.update({ id: '/', path: '/', @@ -46,6 +60,13 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof IndexImport parentRoute: typeof rootRoute } + '/blank': { + id: '/blank' + path: '/blank' + fullPath: '/blank' + preLoaderRoute: typeof BlankImport + parentRoute: typeof rootRoute + } '/count': { id: '/count' path: '/count' @@ -53,6 +74,13 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof CountImport parentRoute: typeof rootRoute } + '/tests': { + id: '/tests' + path: '/tests' + fullPath: '/tests' + preLoaderRoute: typeof TestsImport + parentRoute: typeof rootRoute + } '/time': { id: '/time' path: '/time' @@ -67,41 +95,51 @@ declare module '@tanstack/react-router' { export interface FileRoutesByFullPath { '/': typeof IndexRoute + '/blank': typeof BlankRoute '/count': typeof CountRoute + '/tests': typeof TestsRoute '/time': typeof TimeRoute } export interface FileRoutesByTo { '/': typeof IndexRoute + '/blank': typeof BlankRoute '/count': typeof CountRoute + '/tests': typeof TestsRoute '/time': typeof TimeRoute } export interface FileRoutesById { __root__: typeof rootRoute '/': typeof IndexRoute + '/blank': typeof BlankRoute '/count': typeof CountRoute + '/tests': typeof TestsRoute '/time': typeof TimeRoute } export interface FileRouteTypes { fileRoutesByFullPath: FileRoutesByFullPath - fullPaths: '/' | '/count' | '/time' + fullPaths: '/' | '/blank' | '/count' | '/tests' | '/time' fileRoutesByTo: FileRoutesByTo - to: '/' | '/count' | '/time' - id: '__root__' | '/' | '/count' | '/time' + to: '/' | '/blank' | '/count' | '/tests' | '/time' + id: '__root__' | '/' | '/blank' | '/count' | '/tests' | '/time' fileRoutesById: FileRoutesById } export interface RootRouteChildren { IndexRoute: typeof IndexRoute + BlankRoute: typeof BlankRoute CountRoute: typeof CountRoute + TestsRoute: typeof TestsRoute TimeRoute: typeof TimeRoute } const rootRouteChildren: RootRouteChildren = { IndexRoute: IndexRoute, + BlankRoute: BlankRoute, CountRoute: CountRoute, + TestsRoute: TestsRoute, TimeRoute: TimeRoute, } @@ -116,16 +154,24 @@ export const routeTree = rootRoute "filePath": "__root.tsx", "children": [ "/", + "/blank", "/count", + "/tests", "/time" ] }, "/": { "filePath": "index.tsx" }, + "/blank": { + "filePath": "blank.tsx" + }, "/count": { "filePath": "count.tsx" }, + "/tests": { + "filePath": "tests.tsx" + }, "/time": { "filePath": "time.tsx" } diff --git a/packages/example/src/routes/__root.tsx b/packages/example/src/routes/__root.tsx index beef033..25107eb 100644 --- a/packages/example/src/routes/__root.tsx +++ b/packages/example/src/routes/__root.tsx @@ -17,6 +17,8 @@ function Root() { Index Time Count + Tests + Blank diff --git a/packages/example/src/routes/blank.tsx b/packages/example/src/routes/blank.tsx new file mode 100644 index 0000000..3d1cd68 --- /dev/null +++ b/packages/example/src/routes/blank.tsx @@ -0,0 +1,9 @@ +import { createFileRoute } from '@tanstack/react-router' + +export const Route = createFileRoute('/blank')({ + component: RouteComponent, +}) + +function RouteComponent() { + return
Hello "/blank"!
+} diff --git a/packages/example/src/routes/tests.tsx b/packages/example/src/routes/tests.tsx new file mode 100644 index 0000000..729f1e0 --- /dev/null +++ b/packages/example/src/routes/tests.tsx @@ -0,0 +1,16 @@ +import { R } from "@/reffuse" +import { createFileRoute } from "@tanstack/react-router" +import { Console, Effect } from "effect" + + +export const Route = createFileRoute("/tests")({ + component: RouteComponent +}) + +function RouteComponent() { + R.useMemo(Effect.addFinalizer(() => Console.log("Cleanup!")).pipe( + Effect.map(() => "test") + )) + + return
Hello "/tests"!
+} diff --git a/packages/reffuse/src/Reffuse.ts b/packages/reffuse/src/Reffuse.ts index fd42420..dc1c6c6 100644 --- a/packages/reffuse/src/Reffuse.ts +++ b/packages/reffuse/src/Reffuse.ts @@ -66,6 +66,43 @@ export class Reffuse { * You can disable this behavior by setting `doNotReExecuteOnRuntimeOrContextChange` to `true` in `options`. */ useMemo( + effect: Effect.Effect, + deps?: React.DependencyList, + options?: RenderOptions & ScopeOptions, + ): A { + const runSync = this.useRunSync() + + const [value, scope] = React.useMemo(() => Scope.make(options?.finalizerExecutionStrategy).pipe( + Effect.flatMap(scope => + Effect.provideService(effect, Scope.Scope, scope).pipe( + Effect.map(value => [value, scope] as const) + ) + ), + + runSync, + ), [ + ...options?.doNotReExecuteOnRuntimeOrContextChange ? [] : [runSync], + ...(deps ?? []), + ]) + + React.useEffect(() => { + console.log("effect", value, scope) + // return () => { console.log("cleanup", value, scope); runSync(Scope.close(scope, Exit.void)) } + }, [scope]) + + return value + } + + /** + * Reffuse equivalent to `React.useMemo`. + * + * `useMemo` will only recompute the memoized value by running the given synchronous effect when one of the deps has changed. \ + * Trying to run an asynchronous effect will throw. + * + * Changes to the Reffuse runtime or context will recompute the value in addition to the deps. + * You can disable this behavior by setting `doNotReExecuteOnRuntimeOrContextChange` to `true` in `options`. + */ + useMemoUnscoped( effect: Effect.Effect, deps?: React.DependencyList, options?: RenderOptions, @@ -238,14 +275,14 @@ export class Reffuse { useRef(value: A): SubscriptionRef.SubscriptionRef { - return this.useMemo( + return this.useMemoUnscoped( SubscriptionRef.make(value), [], { doNotReExecuteOnRuntimeOrContextChange: false }, // Do not recreate the ref when the context changes ) } - useRefFromEffect(effect: Effect.Effect): SubscriptionRef.SubscriptionRef { + useRefFromEffect(effect: Effect.Effect): SubscriptionRef.SubscriptionRef { return this.useMemo( effect.pipe(Effect.flatMap(SubscriptionRef.make)), [],