0.2.1 #26

Merged
Thilawyn merged 144 commits from next into master 2025-12-01 23:37:40 +01:00
3 changed files with 93 additions and 18 deletions
Showing only changes of commit cba42bfa52 - Show all commits

View File

@@ -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
})

View File

@@ -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

View 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
})