This commit is contained in:
@@ -408,7 +408,7 @@ export const withRuntime: {
|
|||||||
|
|
||||||
|
|
||||||
export class ScopeMap extends Effect.Service<ScopeMap>()("effect-fc/Component/ScopeMap", {
|
export class ScopeMap extends Effect.Service<ScopeMap>()("effect-fc/Component/ScopeMap", {
|
||||||
effect: Effect.bind(Effect.Do, "ref", () => Ref.make(HashMap.empty<string, ScopeMap.Entry>()))
|
effect: Effect.bind(Effect.Do, "ref", () => Ref.make(HashMap.empty<object, ScopeMap.Entry>()))
|
||||||
}) {}
|
}) {}
|
||||||
|
|
||||||
export namespace ScopeMap {
|
export namespace ScopeMap {
|
||||||
@@ -429,12 +429,12 @@ export const useScope: {
|
|||||||
const runtimeRef = React.useRef<Runtime.Runtime<never>>(null!)
|
const runtimeRef = React.useRef<Runtime.Runtime<never>>(null!)
|
||||||
runtimeRef.current = yield* Effect.runtime()
|
runtimeRef.current = yield* Effect.runtime()
|
||||||
|
|
||||||
const key = React.useId()
|
|
||||||
const scopeMap = yield* ScopeMap as unknown as Effect.Effect<ScopeMap>
|
const scopeMap = yield* ScopeMap as unknown as Effect.Effect<ScopeMap>
|
||||||
|
|
||||||
const scope = React.useMemo(() => Runtime.runSync(runtimeRef.current)(Effect.andThen(
|
const [key, scope] = React.useMemo(() => Runtime.runSync(runtimeRef.current)(Effect.andThen(
|
||||||
scopeMap.ref,
|
Effect.all([Effect.succeed({}), scopeMap.ref]),
|
||||||
map => Option.match(HashMap.get(map, key), {
|
([key, map]) => Effect.andThen(
|
||||||
|
Option.match(HashMap.get(map, key), {
|
||||||
onSome: entry => Effect.succeed(entry.scope),
|
onSome: entry => Effect.succeed(entry.scope),
|
||||||
onNone: () => Effect.tap(
|
onNone: () => Effect.tap(
|
||||||
Scope.make(options?.finalizerExecutionStrategy ?? ExecutionStrategy.sequential),
|
Scope.make(options?.finalizerExecutionStrategy ?? ExecutionStrategy.sequential),
|
||||||
@@ -444,10 +444,12 @@ export const useScope: {
|
|||||||
}))
|
}))
|
||||||
),
|
),
|
||||||
}),
|
}),
|
||||||
|
scope => [key, scope] as const,
|
||||||
|
),
|
||||||
// biome-ignore lint/correctness/useExhaustiveDependencies: use of React.DependencyList
|
// biome-ignore lint/correctness/useExhaustiveDependencies: use of React.DependencyList
|
||||||
)), deps)
|
)), deps)
|
||||||
|
|
||||||
// biome-ignore lint/correctness/useExhaustiveDependencies: only reactive on "scope"
|
// biome-ignore lint/correctness/useExhaustiveDependencies: only reactive on "key"
|
||||||
React.useEffect(() => Runtime.runSync(runtimeRef.current)(scopeMap.ref.pipe(
|
React.useEffect(() => Runtime.runSync(runtimeRef.current)(scopeMap.ref.pipe(
|
||||||
Effect.andThen(HashMap.get(key)),
|
Effect.andThen(HashMap.get(key)),
|
||||||
Effect.tap(entry => Option.match(entry.closeFiber, {
|
Effect.tap(entry => Option.match(entry.closeFiber, {
|
||||||
@@ -469,7 +471,7 @@ export const useScope: {
|
|||||||
})),
|
})),
|
||||||
))
|
))
|
||||||
),
|
),
|
||||||
)), [scope])
|
)), [key])
|
||||||
|
|
||||||
return scope
|
return scope
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import { Route as FormRouteImport } from './routes/form'
|
|||||||
import { Route as BlankRouteImport } from './routes/blank'
|
import { Route as BlankRouteImport } from './routes/blank'
|
||||||
import { Route as IndexRouteImport } from './routes/index'
|
import { Route as IndexRouteImport } from './routes/index'
|
||||||
import { Route as DevMemoRouteImport } from './routes/dev/memo'
|
import { Route as DevMemoRouteImport } from './routes/dev/memo'
|
||||||
|
import { Route as DevContextRouteImport } from './routes/dev/context'
|
||||||
import { Route as DevAsyncRenderingRouteImport } from './routes/dev/async-rendering'
|
import { Route as DevAsyncRenderingRouteImport } from './routes/dev/async-rendering'
|
||||||
|
|
||||||
const FormRoute = FormRouteImport.update({
|
const FormRoute = FormRouteImport.update({
|
||||||
@@ -35,6 +36,11 @@ const DevMemoRoute = DevMemoRouteImport.update({
|
|||||||
path: '/dev/memo',
|
path: '/dev/memo',
|
||||||
getParentRoute: () => rootRouteImport,
|
getParentRoute: () => rootRouteImport,
|
||||||
} as any)
|
} as any)
|
||||||
|
const DevContextRoute = DevContextRouteImport.update({
|
||||||
|
id: '/dev/context',
|
||||||
|
path: '/dev/context',
|
||||||
|
getParentRoute: () => rootRouteImport,
|
||||||
|
} as any)
|
||||||
const DevAsyncRenderingRoute = DevAsyncRenderingRouteImport.update({
|
const DevAsyncRenderingRoute = DevAsyncRenderingRouteImport.update({
|
||||||
id: '/dev/async-rendering',
|
id: '/dev/async-rendering',
|
||||||
path: '/dev/async-rendering',
|
path: '/dev/async-rendering',
|
||||||
@@ -46,6 +52,7 @@ export interface FileRoutesByFullPath {
|
|||||||
'/blank': typeof BlankRoute
|
'/blank': typeof BlankRoute
|
||||||
'/form': typeof FormRoute
|
'/form': typeof FormRoute
|
||||||
'/dev/async-rendering': typeof DevAsyncRenderingRoute
|
'/dev/async-rendering': typeof DevAsyncRenderingRoute
|
||||||
|
'/dev/context': typeof DevContextRoute
|
||||||
'/dev/memo': typeof DevMemoRoute
|
'/dev/memo': typeof DevMemoRoute
|
||||||
}
|
}
|
||||||
export interface FileRoutesByTo {
|
export interface FileRoutesByTo {
|
||||||
@@ -53,6 +60,7 @@ export interface FileRoutesByTo {
|
|||||||
'/blank': typeof BlankRoute
|
'/blank': typeof BlankRoute
|
||||||
'/form': typeof FormRoute
|
'/form': typeof FormRoute
|
||||||
'/dev/async-rendering': typeof DevAsyncRenderingRoute
|
'/dev/async-rendering': typeof DevAsyncRenderingRoute
|
||||||
|
'/dev/context': typeof DevContextRoute
|
||||||
'/dev/memo': typeof DevMemoRoute
|
'/dev/memo': typeof DevMemoRoute
|
||||||
}
|
}
|
||||||
export interface FileRoutesById {
|
export interface FileRoutesById {
|
||||||
@@ -61,19 +69,33 @@ export interface FileRoutesById {
|
|||||||
'/blank': typeof BlankRoute
|
'/blank': typeof BlankRoute
|
||||||
'/form': typeof FormRoute
|
'/form': typeof FormRoute
|
||||||
'/dev/async-rendering': typeof DevAsyncRenderingRoute
|
'/dev/async-rendering': typeof DevAsyncRenderingRoute
|
||||||
|
'/dev/context': typeof DevContextRoute
|
||||||
'/dev/memo': typeof DevMemoRoute
|
'/dev/memo': typeof DevMemoRoute
|
||||||
}
|
}
|
||||||
export interface FileRouteTypes {
|
export interface FileRouteTypes {
|
||||||
fileRoutesByFullPath: FileRoutesByFullPath
|
fileRoutesByFullPath: FileRoutesByFullPath
|
||||||
fullPaths: '/' | '/blank' | '/form' | '/dev/async-rendering' | '/dev/memo'
|
fullPaths:
|
||||||
|
| '/'
|
||||||
|
| '/blank'
|
||||||
|
| '/form'
|
||||||
|
| '/dev/async-rendering'
|
||||||
|
| '/dev/context'
|
||||||
|
| '/dev/memo'
|
||||||
fileRoutesByTo: FileRoutesByTo
|
fileRoutesByTo: FileRoutesByTo
|
||||||
to: '/' | '/blank' | '/form' | '/dev/async-rendering' | '/dev/memo'
|
to:
|
||||||
|
| '/'
|
||||||
|
| '/blank'
|
||||||
|
| '/form'
|
||||||
|
| '/dev/async-rendering'
|
||||||
|
| '/dev/context'
|
||||||
|
| '/dev/memo'
|
||||||
id:
|
id:
|
||||||
| '__root__'
|
| '__root__'
|
||||||
| '/'
|
| '/'
|
||||||
| '/blank'
|
| '/blank'
|
||||||
| '/form'
|
| '/form'
|
||||||
| '/dev/async-rendering'
|
| '/dev/async-rendering'
|
||||||
|
| '/dev/context'
|
||||||
| '/dev/memo'
|
| '/dev/memo'
|
||||||
fileRoutesById: FileRoutesById
|
fileRoutesById: FileRoutesById
|
||||||
}
|
}
|
||||||
@@ -82,6 +104,7 @@ export interface RootRouteChildren {
|
|||||||
BlankRoute: typeof BlankRoute
|
BlankRoute: typeof BlankRoute
|
||||||
FormRoute: typeof FormRoute
|
FormRoute: typeof FormRoute
|
||||||
DevAsyncRenderingRoute: typeof DevAsyncRenderingRoute
|
DevAsyncRenderingRoute: typeof DevAsyncRenderingRoute
|
||||||
|
DevContextRoute: typeof DevContextRoute
|
||||||
DevMemoRoute: typeof DevMemoRoute
|
DevMemoRoute: typeof DevMemoRoute
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -115,6 +138,13 @@ declare module '@tanstack/react-router' {
|
|||||||
preLoaderRoute: typeof DevMemoRouteImport
|
preLoaderRoute: typeof DevMemoRouteImport
|
||||||
parentRoute: typeof rootRouteImport
|
parentRoute: typeof rootRouteImport
|
||||||
}
|
}
|
||||||
|
'/dev/context': {
|
||||||
|
id: '/dev/context'
|
||||||
|
path: '/dev/context'
|
||||||
|
fullPath: '/dev/context'
|
||||||
|
preLoaderRoute: typeof DevContextRouteImport
|
||||||
|
parentRoute: typeof rootRouteImport
|
||||||
|
}
|
||||||
'/dev/async-rendering': {
|
'/dev/async-rendering': {
|
||||||
id: '/dev/async-rendering'
|
id: '/dev/async-rendering'
|
||||||
path: '/dev/async-rendering'
|
path: '/dev/async-rendering'
|
||||||
@@ -130,6 +160,7 @@ const rootRouteChildren: RootRouteChildren = {
|
|||||||
BlankRoute: BlankRoute,
|
BlankRoute: BlankRoute,
|
||||||
FormRoute: FormRoute,
|
FormRoute: FormRoute,
|
||||||
DevAsyncRenderingRoute: DevAsyncRenderingRoute,
|
DevAsyncRenderingRoute: DevAsyncRenderingRoute,
|
||||||
|
DevContextRoute: DevContextRoute,
|
||||||
DevMemoRoute: DevMemoRoute,
|
DevMemoRoute: DevMemoRoute,
|
||||||
}
|
}
|
||||||
export const routeTree = rootRouteImport
|
export const routeTree = rootRouteImport
|
||||||
|
|||||||
42
packages/example/src/routes/dev/context.tsx
Normal file
42
packages/example/src/routes/dev/context.tsx
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
import { Container, Flex, Text, TextField } from "@radix-ui/themes"
|
||||||
|
import { createFileRoute } from "@tanstack/react-router"
|
||||||
|
import { Console, Effect } from "effect"
|
||||||
|
import { Component } from "effect-fc"
|
||||||
|
import * as React from "react"
|
||||||
|
import { runtime } from "@/runtime"
|
||||||
|
|
||||||
|
|
||||||
|
class SubService extends Effect.Service<SubService>()("SubService", {
|
||||||
|
effect: (value: string) => Effect.succeed({ value })
|
||||||
|
}) {}
|
||||||
|
|
||||||
|
const SubComponent = Component.makeUntraced("SubComponent")(function*() {
|
||||||
|
const service = yield* SubService
|
||||||
|
yield* Component.useOnMount(() => Effect.gen(function*() {
|
||||||
|
yield* Effect.addFinalizer(() => Console.log("SubComponent unmounted"))
|
||||||
|
yield* Console.log("SubComponent mounted")
|
||||||
|
}))
|
||||||
|
|
||||||
|
return <Text>{service.value}</Text>
|
||||||
|
})
|
||||||
|
|
||||||
|
const ContextView = Component.makeUntraced("ContextView")(function*() {
|
||||||
|
const [serviceValue, setServiceValue] = React.useState("test")
|
||||||
|
const SubServiceLayer = React.useMemo(() => SubService.Default(serviceValue), [serviceValue])
|
||||||
|
const SubComponentFC = yield* Effect.provide(SubComponent, yield* Component.useContext(SubServiceLayer))
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Container>
|
||||||
|
<Flex direction="column" align="center">
|
||||||
|
<TextField.Root value={serviceValue} onChange={e => setServiceValue(e.target.value)} />
|
||||||
|
<SubComponentFC />
|
||||||
|
</Flex>
|
||||||
|
</Container>
|
||||||
|
)
|
||||||
|
}).pipe(
|
||||||
|
Component.withRuntime(runtime.context)
|
||||||
|
)
|
||||||
|
|
||||||
|
export const Route = createFileRoute("/dev/context")({
|
||||||
|
component: ContextView
|
||||||
|
})
|
||||||
Reference in New Issue
Block a user