0.1.2 #3

Merged
Thilawyn merged 28 commits from next into master 2025-07-23 21:28:25 +02:00
3 changed files with 33 additions and 7 deletions
Showing only changes of commit 7fdb2a799c - Show all commits

View File

@@ -1,4 +1,4 @@
import { Context, Effect, ExecutionStrategy, Function, Pipeable, Predicate, Runtime, Scope, String, Tracer, type Utils } from "effect"
import { Context, Effect, Effectable, ExecutionStrategy, Function, Predicate, Runtime, Scope, String, Tracer, type Utils } from "effect"
import * as React from "react"
import * as Hook from "./Hook.js"
import * as Memoized from "./Memoized.js"
@@ -7,7 +7,7 @@ import * as Memoized from "./Memoized.js"
export const TypeId: unique symbol = Symbol.for("effect-fc/Component")
export type TypeId = typeof TypeId
export interface Component<E, R, P extends {}> extends Pipeable.Pipeable, Component.Options {
export interface Component<E, R, P extends {}> extends Effect.Effect<React.FC<P>, never, R>, Component.Options {
new(_: never): {}
readonly [TypeId]: TypeId
/** @internal */
@@ -30,8 +30,34 @@ export namespace Component {
const ComponentProto = Object.freeze({
...Effectable.CommitPrototype,
[TypeId]: TypeId,
pipe() { return Pipeable.pipeArguments(this, arguments) },
commit: Effect.fnUntraced(function* <E, R, P extends {}>(this: Component<E, R, P>) {
const self = this
const runtimeRef = React.useRef<Runtime.Runtime<Exclude<R, Scope.Scope>>>(null!)
runtimeRef.current = yield* Effect.runtime<Exclude<R, Scope.Scope>>()
return React.useCallback(function ScopeProvider(props: P) {
const scope = Runtime.runSync(runtimeRef.current)(Hook.useScope(
Array.from(
Context.omit(...nonReactiveTags)(runtimeRef.current.context).unsafeMap.values()
),
self,
))
const FC = React.useMemo(() => {
const f = self.makeFunctionComponent(runtimeRef, scope)
f.displayName = self.displayName ?? "Anonymous"
return Memoized.isMemoized(self)
? React.memo(f, self.propsAreEqual)
: f
}, [scope])
return React.createElement(FC, props)
}, [])
}),
makeFunctionComponent(
this: Component<any, any, any>,
@@ -52,7 +78,7 @@ const defaultOptions = {
const nonReactiveTags = [Tracer.ParentSpan] as const
export const isComponent = (u: unknown): u is Component<any, any, any> => Predicate.hasProperty(u, TypeId)
export const isComponent = (u: unknown): u is Component<unknown, unknown, {}> => Predicate.hasProperty(u, TypeId)
export namespace make {
export type Gen = {

View File

@@ -21,7 +21,7 @@ const MemoizedProto = Object.freeze({
} as const)
export const isMemoized = (u: unknown): u is Memoized<any> => Predicate.hasProperty(u, TypeId)
export const isMemoized = (u: unknown): u is Memoized<unknown> => Predicate.hasProperty(u, TypeId)
export const memo = <T extends Component.Component<any, any, any>>(
self: T

View File

@@ -9,8 +9,8 @@ import * as React from "react"
// Generator version
const RouteComponent = Component.make(function* AsyncRendering() {
const VMemoizedAsyncComponent = yield* Component.useFC(MemoizedAsyncComponent)
const VAsyncComponent = yield* Component.useFC(AsyncComponent)
const VMemoizedAsyncComponent = yield* MemoizedAsyncComponent
const VAsyncComponent = yield* AsyncComponent
const [input, setInput] = React.useState("")
return (