0.2.2 #31
@@ -408,7 +408,7 @@ export const withRuntime: {
|
||||
|
||||
|
||||
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 {
|
||||
@@ -429,25 +429,27 @@ export const useScope: {
|
||||
const runtimeRef = React.useRef<Runtime.Runtime<never>>(null!)
|
||||
runtimeRef.current = yield* Effect.runtime()
|
||||
|
||||
const key = React.useId()
|
||||
const scopeMap = yield* ScopeMap as unknown as Effect.Effect<ScopeMap>
|
||||
|
||||
const scope = React.useMemo(() => Runtime.runSync(runtimeRef.current)(Effect.andThen(
|
||||
scopeMap.ref,
|
||||
map => Option.match(HashMap.get(map, key), {
|
||||
onSome: entry => Effect.succeed(entry.scope),
|
||||
onNone: () => Effect.tap(
|
||||
Scope.make(options?.finalizerExecutionStrategy ?? ExecutionStrategy.sequential),
|
||||
scope => Ref.update(scopeMap.ref, HashMap.set(key, {
|
||||
scope,
|
||||
closeFiber: Option.none(),
|
||||
}))
|
||||
),
|
||||
}),
|
||||
const [key, scope] = React.useMemo(() => Runtime.runSync(runtimeRef.current)(Effect.andThen(
|
||||
Effect.all([Effect.succeed({}), scopeMap.ref]),
|
||||
([key, map]) => Effect.andThen(
|
||||
Option.match(HashMap.get(map, key), {
|
||||
onSome: entry => Effect.succeed(entry.scope),
|
||||
onNone: () => Effect.tap(
|
||||
Scope.make(options?.finalizerExecutionStrategy ?? ExecutionStrategy.sequential),
|
||||
scope => Ref.update(scopeMap.ref, HashMap.set(key, {
|
||||
scope,
|
||||
closeFiber: Option.none(),
|
||||
}))
|
||||
),
|
||||
}),
|
||||
scope => [key, scope] as const,
|
||||
),
|
||||
// biome-ignore lint/correctness/useExhaustiveDependencies: use of React.DependencyList
|
||||
)), 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(
|
||||
Effect.andThen(HashMap.get(key)),
|
||||
Effect.tap(entry => Option.match(entry.closeFiber, {
|
||||
@@ -469,7 +471,7 @@ export const useScope: {
|
||||
})),
|
||||
))
|
||||
),
|
||||
)), [scope])
|
||||
)), [key])
|
||||
|
||||
return scope
|
||||
})
|
||||
|
||||
@@ -13,6 +13,7 @@ import { Route as FormRouteImport } from './routes/form'
|
||||
import { Route as BlankRouteImport } from './routes/blank'
|
||||
import { Route as IndexRouteImport } from './routes/index'
|
||||
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'
|
||||
|
||||
const FormRoute = FormRouteImport.update({
|
||||
@@ -35,6 +36,11 @@ const DevMemoRoute = DevMemoRouteImport.update({
|
||||
path: '/dev/memo',
|
||||
getParentRoute: () => rootRouteImport,
|
||||
} as any)
|
||||
const DevContextRoute = DevContextRouteImport.update({
|
||||
id: '/dev/context',
|
||||
path: '/dev/context',
|
||||
getParentRoute: () => rootRouteImport,
|
||||
} as any)
|
||||
const DevAsyncRenderingRoute = DevAsyncRenderingRouteImport.update({
|
||||
id: '/dev/async-rendering',
|
||||
path: '/dev/async-rendering',
|
||||
@@ -46,6 +52,7 @@ export interface FileRoutesByFullPath {
|
||||
'/blank': typeof BlankRoute
|
||||
'/form': typeof FormRoute
|
||||
'/dev/async-rendering': typeof DevAsyncRenderingRoute
|
||||
'/dev/context': typeof DevContextRoute
|
||||
'/dev/memo': typeof DevMemoRoute
|
||||
}
|
||||
export interface FileRoutesByTo {
|
||||
@@ -53,6 +60,7 @@ export interface FileRoutesByTo {
|
||||
'/blank': typeof BlankRoute
|
||||
'/form': typeof FormRoute
|
||||
'/dev/async-rendering': typeof DevAsyncRenderingRoute
|
||||
'/dev/context': typeof DevContextRoute
|
||||
'/dev/memo': typeof DevMemoRoute
|
||||
}
|
||||
export interface FileRoutesById {
|
||||
@@ -61,19 +69,33 @@ export interface FileRoutesById {
|
||||
'/blank': typeof BlankRoute
|
||||
'/form': typeof FormRoute
|
||||
'/dev/async-rendering': typeof DevAsyncRenderingRoute
|
||||
'/dev/context': typeof DevContextRoute
|
||||
'/dev/memo': typeof DevMemoRoute
|
||||
}
|
||||
export interface FileRouteTypes {
|
||||
fileRoutesByFullPath: FileRoutesByFullPath
|
||||
fullPaths: '/' | '/blank' | '/form' | '/dev/async-rendering' | '/dev/memo'
|
||||
fullPaths:
|
||||
| '/'
|
||||
| '/blank'
|
||||
| '/form'
|
||||
| '/dev/async-rendering'
|
||||
| '/dev/context'
|
||||
| '/dev/memo'
|
||||
fileRoutesByTo: FileRoutesByTo
|
||||
to: '/' | '/blank' | '/form' | '/dev/async-rendering' | '/dev/memo'
|
||||
to:
|
||||
| '/'
|
||||
| '/blank'
|
||||
| '/form'
|
||||
| '/dev/async-rendering'
|
||||
| '/dev/context'
|
||||
| '/dev/memo'
|
||||
id:
|
||||
| '__root__'
|
||||
| '/'
|
||||
| '/blank'
|
||||
| '/form'
|
||||
| '/dev/async-rendering'
|
||||
| '/dev/context'
|
||||
| '/dev/memo'
|
||||
fileRoutesById: FileRoutesById
|
||||
}
|
||||
@@ -82,6 +104,7 @@ export interface RootRouteChildren {
|
||||
BlankRoute: typeof BlankRoute
|
||||
FormRoute: typeof FormRoute
|
||||
DevAsyncRenderingRoute: typeof DevAsyncRenderingRoute
|
||||
DevContextRoute: typeof DevContextRoute
|
||||
DevMemoRoute: typeof DevMemoRoute
|
||||
}
|
||||
|
||||
@@ -115,6 +138,13 @@ declare module '@tanstack/react-router' {
|
||||
preLoaderRoute: typeof DevMemoRouteImport
|
||||
parentRoute: typeof rootRouteImport
|
||||
}
|
||||
'/dev/context': {
|
||||
id: '/dev/context'
|
||||
path: '/dev/context'
|
||||
fullPath: '/dev/context'
|
||||
preLoaderRoute: typeof DevContextRouteImport
|
||||
parentRoute: typeof rootRouteImport
|
||||
}
|
||||
'/dev/async-rendering': {
|
||||
id: '/dev/async-rendering'
|
||||
path: '/dev/async-rendering'
|
||||
@@ -130,6 +160,7 @@ const rootRouteChildren: RootRouteChildren = {
|
||||
BlankRoute: BlankRoute,
|
||||
FormRoute: FormRoute,
|
||||
DevAsyncRenderingRoute: DevAsyncRenderingRoute,
|
||||
DevContextRoute: DevContextRoute,
|
||||
DevMemoRoute: DevMemoRoute,
|
||||
}
|
||||
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