useMemo scoping attempt
All checks were successful
Lint / lint (push) Successful in 10s

This commit is contained in:
Julien Valverdé
2025-01-19 00:26:30 +01:00
parent 707f83ce87
commit bca9b6d0d4
5 changed files with 115 additions and 5 deletions

View File

@@ -12,7 +12,9 @@
import { Route as rootRoute } from './routes/__root' import { Route as rootRoute } from './routes/__root'
import { Route as TimeImport } from './routes/time' import { Route as TimeImport } from './routes/time'
import { Route as TestsImport } from './routes/tests'
import { Route as CountImport } from './routes/count' import { Route as CountImport } from './routes/count'
import { Route as BlankImport } from './routes/blank'
import { Route as IndexImport } from './routes/index' import { Route as IndexImport } from './routes/index'
// Create/Update Routes // Create/Update Routes
@@ -23,12 +25,24 @@ const TimeRoute = TimeImport.update({
getParentRoute: () => rootRoute, getParentRoute: () => rootRoute,
} as any) } as any)
const TestsRoute = TestsImport.update({
id: '/tests',
path: '/tests',
getParentRoute: () => rootRoute,
} as any)
const CountRoute = CountImport.update({ const CountRoute = CountImport.update({
id: '/count', id: '/count',
path: '/count', path: '/count',
getParentRoute: () => rootRoute, getParentRoute: () => rootRoute,
} as any) } as any)
const BlankRoute = BlankImport.update({
id: '/blank',
path: '/blank',
getParentRoute: () => rootRoute,
} as any)
const IndexRoute = IndexImport.update({ const IndexRoute = IndexImport.update({
id: '/', id: '/',
path: '/', path: '/',
@@ -46,6 +60,13 @@ declare module '@tanstack/react-router' {
preLoaderRoute: typeof IndexImport preLoaderRoute: typeof IndexImport
parentRoute: typeof rootRoute parentRoute: typeof rootRoute
} }
'/blank': {
id: '/blank'
path: '/blank'
fullPath: '/blank'
preLoaderRoute: typeof BlankImport
parentRoute: typeof rootRoute
}
'/count': { '/count': {
id: '/count' id: '/count'
path: '/count' path: '/count'
@@ -53,6 +74,13 @@ declare module '@tanstack/react-router' {
preLoaderRoute: typeof CountImport preLoaderRoute: typeof CountImport
parentRoute: typeof rootRoute parentRoute: typeof rootRoute
} }
'/tests': {
id: '/tests'
path: '/tests'
fullPath: '/tests'
preLoaderRoute: typeof TestsImport
parentRoute: typeof rootRoute
}
'/time': { '/time': {
id: '/time' id: '/time'
path: '/time' path: '/time'
@@ -67,41 +95,51 @@ declare module '@tanstack/react-router' {
export interface FileRoutesByFullPath { export interface FileRoutesByFullPath {
'/': typeof IndexRoute '/': typeof IndexRoute
'/blank': typeof BlankRoute
'/count': typeof CountRoute '/count': typeof CountRoute
'/tests': typeof TestsRoute
'/time': typeof TimeRoute '/time': typeof TimeRoute
} }
export interface FileRoutesByTo { export interface FileRoutesByTo {
'/': typeof IndexRoute '/': typeof IndexRoute
'/blank': typeof BlankRoute
'/count': typeof CountRoute '/count': typeof CountRoute
'/tests': typeof TestsRoute
'/time': typeof TimeRoute '/time': typeof TimeRoute
} }
export interface FileRoutesById { export interface FileRoutesById {
__root__: typeof rootRoute __root__: typeof rootRoute
'/': typeof IndexRoute '/': typeof IndexRoute
'/blank': typeof BlankRoute
'/count': typeof CountRoute '/count': typeof CountRoute
'/tests': typeof TestsRoute
'/time': typeof TimeRoute '/time': typeof TimeRoute
} }
export interface FileRouteTypes { export interface FileRouteTypes {
fileRoutesByFullPath: FileRoutesByFullPath fileRoutesByFullPath: FileRoutesByFullPath
fullPaths: '/' | '/count' | '/time' fullPaths: '/' | '/blank' | '/count' | '/tests' | '/time'
fileRoutesByTo: FileRoutesByTo fileRoutesByTo: FileRoutesByTo
to: '/' | '/count' | '/time' to: '/' | '/blank' | '/count' | '/tests' | '/time'
id: '__root__' | '/' | '/count' | '/time' id: '__root__' | '/' | '/blank' | '/count' | '/tests' | '/time'
fileRoutesById: FileRoutesById fileRoutesById: FileRoutesById
} }
export interface RootRouteChildren { export interface RootRouteChildren {
IndexRoute: typeof IndexRoute IndexRoute: typeof IndexRoute
BlankRoute: typeof BlankRoute
CountRoute: typeof CountRoute CountRoute: typeof CountRoute
TestsRoute: typeof TestsRoute
TimeRoute: typeof TimeRoute TimeRoute: typeof TimeRoute
} }
const rootRouteChildren: RootRouteChildren = { const rootRouteChildren: RootRouteChildren = {
IndexRoute: IndexRoute, IndexRoute: IndexRoute,
BlankRoute: BlankRoute,
CountRoute: CountRoute, CountRoute: CountRoute,
TestsRoute: TestsRoute,
TimeRoute: TimeRoute, TimeRoute: TimeRoute,
} }
@@ -116,16 +154,24 @@ export const routeTree = rootRoute
"filePath": "__root.tsx", "filePath": "__root.tsx",
"children": [ "children": [
"/", "/",
"/blank",
"/count", "/count",
"/tests",
"/time" "/time"
] ]
}, },
"/": { "/": {
"filePath": "index.tsx" "filePath": "index.tsx"
}, },
"/blank": {
"filePath": "blank.tsx"
},
"/count": { "/count": {
"filePath": "count.tsx" "filePath": "count.tsx"
}, },
"/tests": {
"filePath": "tests.tsx"
},
"/time": { "/time": {
"filePath": "time.tsx" "filePath": "time.tsx"
} }

View File

@@ -17,6 +17,8 @@ function Root() {
<Link to="/">Index</Link> <Link to="/">Index</Link>
<Link to="/time">Time</Link> <Link to="/time">Time</Link>
<Link to="/count">Count</Link> <Link to="/count">Count</Link>
<Link to="/tests">Tests</Link>
<Link to="/blank">Blank</Link>
</Flex> </Flex>
</Container> </Container>

View File

@@ -0,0 +1,9 @@
import { createFileRoute } from '@tanstack/react-router'
export const Route = createFileRoute('/blank')({
component: RouteComponent,
})
function RouteComponent() {
return <div>Hello "/blank"!</div>
}

View File

@@ -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 <div>Hello "/tests"!</div>
}

View File

@@ -66,6 +66,43 @@ export class Reffuse<R> {
* You can disable this behavior by setting `doNotReExecuteOnRuntimeOrContextChange` to `true` in `options`. * You can disable this behavior by setting `doNotReExecuteOnRuntimeOrContextChange` to `true` in `options`.
*/ */
useMemo<A, E>( useMemo<A, E>(
effect: Effect.Effect<A, E, R | Scope.Scope>,
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<A, E>(
effect: Effect.Effect<A, E, R>, effect: Effect.Effect<A, E, R>,
deps?: React.DependencyList, deps?: React.DependencyList,
options?: RenderOptions, options?: RenderOptions,
@@ -238,14 +275,14 @@ export class Reffuse<R> {
useRef<A>(value: A): SubscriptionRef.SubscriptionRef<A> { useRef<A>(value: A): SubscriptionRef.SubscriptionRef<A> {
return this.useMemo( return this.useMemoUnscoped(
SubscriptionRef.make(value), SubscriptionRef.make(value),
[], [],
{ doNotReExecuteOnRuntimeOrContextChange: false }, // Do not recreate the ref when the context changes { doNotReExecuteOnRuntimeOrContextChange: false }, // Do not recreate the ref when the context changes
) )
} }
useRefFromEffect<A, E>(effect: Effect.Effect<A, E, R>): SubscriptionRef.SubscriptionRef<A> { useRefFromEffect<A, E>(effect: Effect.Effect<A, E, R | Scope.Scope>): SubscriptionRef.SubscriptionRef<A> {
return this.useMemo( return this.useMemo(
effect.pipe(Effect.flatMap(SubscriptionRef.make)), effect.pipe(Effect.flatMap(SubscriptionRef.make)),
[], [],