From 8c1fed78003caa55e0ae6467524d65af5c978557 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Valverd=C3=A9?= Date: Wed, 24 Sep 2025 00:41:42 +0200 Subject: [PATCH] Component refactoring --- packages/effect-fc/src/Component.ts | 40 ++++++++++--------- .../example/src/lib/input/TextAreaInput.tsx | 2 +- .../example/src/lib/input/TextFieldInput.tsx | 2 +- .../src/routes/dev/async-rendering.tsx | 4 +- packages/example/src/routes/dev/input.tsx | 10 ++--- packages/example/src/routes/dev/memo.tsx | 4 +- packages/example/src/routes/form.tsx | 2 +- packages/example/src/routes/index.tsx | 2 +- packages/example/src/todo/Todo.tsx | 2 +- packages/example/src/todo/Todos.tsx | 2 +- 10 files changed, 37 insertions(+), 33 deletions(-) diff --git a/packages/effect-fc/src/Component.ts b/packages/effect-fc/src/Component.ts index 6c36f5e..c0ddc6f 100644 --- a/packages/effect-fc/src/Component.ts +++ b/packages/effect-fc/src/Component.ts @@ -1,4 +1,4 @@ -import { Context, Effect, Effectable, ExecutionStrategy, Function, Predicate, Runtime, Scope, String, Tracer, type Types, type Utils } from "effect" +import { Context, Effect, Effectable, ExecutionStrategy, Function, Predicate, Runtime, Scope, Tracer, type Types, type Utils } from "effect" import * as React from "react" import { Hooks } from "./hooks/index.js" import * as Memo from "./Memo.js" @@ -331,13 +331,9 @@ export const make: ( ) => make.Gen & make.NonGen) ) = (spanNameOrBody: Function | string, ...pipeables: any[]): any => { if (typeof spanNameOrBody !== "string") { - const displayName = displayNameFromBody(spanNameOrBody) return Object.setPrototypeOf( Object.assign(function() {}, defaultOptions, { - body: displayName - ? Effect.fn(displayName)(spanNameOrBody as any, ...pipeables as []) - : Effect.fn(spanNameOrBody as any, ...pipeables), - displayName, + body: Effect.fn(spanNameOrBody as any, ...pipeables), }), ComponentProto, ) @@ -347,26 +343,34 @@ export const make: ( return (body: any, ...pipeables: any[]) => Object.setPrototypeOf( Object.assign(function() {}, defaultOptions, { body: Effect.fn(spanNameOrBody, spanOptions)(body, ...pipeables as []), - displayName: displayNameFromBody(body) ?? spanNameOrBody, + displayName: spanNameOrBody, }), ComponentProto, ) } } -export const makeUntraced: make.Gen & make.NonGen = ( - body: Function, - ...pipeables: any[] -) => Object.setPrototypeOf( - Object.assign(function() {}, defaultOptions, { - body: Effect.fnUntraced(body as any, ...pipeables as []), - displayName: displayNameFromBody(body), - }), - ComponentProto, +export const makeUntraced: ( + & make.Gen + & make.NonGen + & ((name: string) => make.Gen & make.NonGen) +) = (spanNameOrBody: Function | string, ...pipeables: any[]): any => ( + typeof spanNameOrBody !== "string" + ? Object.setPrototypeOf( + Object.assign(function() {}, defaultOptions, { + body: Effect.fnUntraced(spanNameOrBody as any, ...pipeables as []), + }), + ComponentProto, + ) + : (body: any, ...pipeables: any[]) => Object.setPrototypeOf( + Object.assign(function() {}, defaultOptions, { + body: Effect.fnUntraced(body, ...pipeables as []), + displayName: spanNameOrBody, + }), + ComponentProto, + ) ) -const displayNameFromBody = (body: Function) => !String.isEmpty(body.name) ? body.name : undefined - export const withOptions: { >( options: Partial diff --git a/packages/example/src/lib/input/TextAreaInput.tsx b/packages/example/src/lib/input/TextAreaInput.tsx index 9bfeb37..d4413fd 100644 --- a/packages/example/src/lib/input/TextAreaInput.tsx +++ b/packages/example/src/lib/input/TextAreaInput.tsx @@ -15,7 +15,7 @@ export const TextAreaInput = (options: { React.JSX.Element, ParseResult.ParseError, R -> => Component.makeUntraced(function* TextFieldInput(props) { +> => Component.makeUntraced("TextFieldInput")(function* TextFieldInput(props) { const input = yield* useInput({ ...options, ...props }) const issue = React.useMemo(() => input.error.pipe( Option.map(ParseResult.ArrayFormatter.formatErrorSync), diff --git a/packages/example/src/lib/input/TextFieldInput.tsx b/packages/example/src/lib/input/TextFieldInput.tsx index b3c475e..d5f8432 100644 --- a/packages/example/src/lib/input/TextFieldInput.tsx +++ b/packages/example/src/lib/input/TextFieldInput.tsx @@ -18,7 +18,7 @@ export const TextFieldInput = (options: { readonly optional?: O readonly schema: Schema.Schema readonly equivalence?: Equivalence.Equivalence -}) => Component.makeUntraced(function* TextFieldInput(props: O extends true +}) => Component.makeUntraced("TextFieldInput")(function* TextFieldInput(props: O extends true ? TextFieldOptionalInputProps : TextFieldInputProps ) { diff --git a/packages/example/src/routes/dev/async-rendering.tsx b/packages/example/src/routes/dev/async-rendering.tsx index 3728739..fadd316 100644 --- a/packages/example/src/routes/dev/async-rendering.tsx +++ b/packages/example/src/routes/dev/async-rendering.tsx @@ -51,7 +51,7 @@ const RouteComponent = Component.makeUntraced(function* AsyncRendering() { // ) -class AsyncComponent extends Component.makeUntraced(function* AsyncComponent() { +class AsyncComponent extends Component.makeUntraced("AsyncComponent")(function*() { const SubComponentFC = yield* SubComponent yield* Effect.sleep("500 millis") // Async operation @@ -69,7 +69,7 @@ class AsyncComponent extends Component.makeUntraced(function* AsyncComponent() { ) {} class MemoizedAsyncComponent extends Memo.memo(AsyncComponent) {} -class SubComponent extends Component.makeUntraced(function* SubComponent() { +class SubComponent extends Component.makeUntraced("SubComponent")(function*() { const [state] = React.useState(yield* Hooks.useOnce(() => Effect.provide(makeUuid4, GetRandomValues.CryptoRandom))) return {state} }) {} diff --git a/packages/example/src/routes/dev/input.tsx b/packages/example/src/routes/dev/input.tsx index 2514963..9a8a53f 100644 --- a/packages/example/src/routes/dev/input.tsx +++ b/packages/example/src/routes/dev/input.tsx @@ -4,7 +4,7 @@ import { Container } from "@radix-ui/themes" import { createFileRoute } from "@tanstack/react-router" import { Schema, SubscriptionRef } from "effect" import { Component, Memo } from "effect-fc" -import { useInput, useOnce, useRefState } from "effect-fc/hooks" +import { useOnce } from "effect-fc/hooks" const IntFromString = Schema.NumberFromString.pipe(Schema.int()) @@ -12,18 +12,18 @@ const IntFromString = Schema.NumberFromString.pipe(Schema.int()) const IntTextFieldInput = TextFieldInput({ schema: IntFromString }) const StringTextFieldInput = TextFieldInput({ schema: Schema.String }) -const Input = Component.makeUntraced(function* Input() { +const Input = Component.makeUntraced("Input")(function*() { const IntTextFieldInputFC = yield* IntTextFieldInput const StringTextFieldInputFC = yield* StringTextFieldInput const intRef1 = yield* useOnce(() => SubscriptionRef.make(0)) - const intRef2 = yield* useOnce(() => SubscriptionRef.make(0)) + // const intRef2 = yield* useOnce(() => SubscriptionRef.make(0)) const stringRef = yield* useOnce(() => SubscriptionRef.make("")) // yield* useFork(() => Stream.runForEach(intRef1.changes, Console.log), [intRef1]) - const input2 = yield* useInput({ schema: IntFromString, ref: intRef2 }) + // const input2 = yield* useInput({ schema: IntFromString, ref: intRef2 }) - const [str, setStr] = yield* useRefState(stringRef) + // const [str, setStr] = yield* useRefState(stringRef) return ( diff --git a/packages/example/src/routes/dev/memo.tsx b/packages/example/src/routes/dev/memo.tsx index 4413107..b81f132 100644 --- a/packages/example/src/routes/dev/memo.tsx +++ b/packages/example/src/routes/dev/memo.tsx @@ -7,7 +7,7 @@ import { Component, Memo } from "effect-fc" import * as React from "react" -const RouteComponent = Component.makeUntraced(function* RouteComponent() { +const RouteComponent = Component.makeUntraced("RouteComponent")(function*() { const [value, setValue] = React.useState("") return ( @@ -25,7 +25,7 @@ const RouteComponent = Component.makeUntraced(function* RouteComponent() { Component.withRuntime(runtime.context) ) -class SubComponent extends Component.makeUntraced(function* SubComponent() { +class SubComponent extends Component.makeUntraced("SubComponent")(function*() { const id = yield* makeUuid4.pipe(Effect.provide(GetRandomValues.CryptoRandom)) return {id} }) {} diff --git a/packages/example/src/routes/form.tsx b/packages/example/src/routes/form.tsx index f0d452e..850f56d 100644 --- a/packages/example/src/routes/form.tsx +++ b/packages/example/src/routes/form.tsx @@ -28,7 +28,7 @@ class RegisterForm extends Effect.Service()("RegisterForm", { }) }) {} -class RegisterPage extends Component.makeUntraced(function* RegisterPage() { +class RegisterPage extends Component.makeUntraced("RegisterPage")(function*() { const form = yield* RegisterForm const emailInput = yield* Form.useInput(form, ["email"], { debounce: "200 millis" }) const passwordInput = yield* Form.useInput(form, ["password"], { debounce: "200 millis" }) diff --git a/packages/example/src/routes/index.tsx b/packages/example/src/routes/index.tsx index ea8bc2e..3bf279f 100644 --- a/packages/example/src/routes/index.tsx +++ b/packages/example/src/routes/index.tsx @@ -9,7 +9,7 @@ import { useContext } from "effect-fc/hooks" const TodosStateLive = TodosState.Default("todos") -const Index = Component.makeUntraced(function* Index() { +const Index = Component.makeUntraced("Index")(function*() { const context = yield* useContext(TodosStateLive, { finalizerExecutionMode: "fork" }) const TodosFC = yield* Effect.provide(Todos, context) diff --git a/packages/example/src/todo/Todo.tsx b/packages/example/src/todo/Todo.tsx index cc8a6cc..62ed92c 100644 --- a/packages/example/src/todo/Todo.tsx +++ b/packages/example/src/todo/Todo.tsx @@ -31,7 +31,7 @@ export type TodoProps = ( | { readonly _tag: "edit", readonly id: string } ) -export class Todo extends Component.makeUntraced(function* Todo(props: TodoProps) { +export class Todo extends Component.makeUntraced("Todo")(function*(props: TodoProps) { const runtime = yield* Effect.runtime() const state = yield* TodosState diff --git a/packages/example/src/todo/Todos.tsx b/packages/example/src/todo/Todos.tsx index e533f24..25aa06d 100644 --- a/packages/example/src/todo/Todos.tsx +++ b/packages/example/src/todo/Todos.tsx @@ -6,7 +6,7 @@ import { Todo } from "./Todo" import { TodosState } from "./TodosState.service" -export class Todos extends Component.makeUntraced(function* Todos() { +export class Todos extends Component.makeUntraced("Todos")(function*() { const state = yield* TodosState const [todos] = yield* useSubscribe(state.ref)