From 483e90dc67de1149eea9bfef357832b8ad0b7a2c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Valverd=C3=A9?= Date: Wed, 27 Aug 2025 05:25:22 +0200 Subject: [PATCH] Form done --- packages/effect-fc/src/Form.ts | 85 ++++++++++++++++------------------ 1 file changed, 39 insertions(+), 46 deletions(-) diff --git a/packages/effect-fc/src/Form.ts b/packages/effect-fc/src/Form.ts index 127a54b..1237c9e 100644 --- a/packages/effect-fc/src/Form.ts +++ b/packages/effect-fc/src/Form.ts @@ -1,8 +1,8 @@ -import { Array, Effect, Either, Equivalence, flow, Option, ParseResult, Pipeable, Schema, Stream, Subscribable, SubscriptionRef } from "effect" +import { Array, Effect, Equivalence, flow, Option, ParseResult, Pipeable, Schema, Stream, type Subscribable, SubscriptionRef } from "effect" import type { NoSuchElementException } from "effect/Cause" import * as React from "react" import { Hooks } from "./hooks/index.js" -import { PropertyPath, Subscribable as SubscribableInternal, SubscriptionSubRef } from "./types/index.js" +import { PropertyPath, Subscribable as SubscribableInternal } from "./types/index.js" export const TypeId: unique symbol = Symbol.for("effect-fc/Form") @@ -13,12 +13,12 @@ extends Pipeable.Pipeable { readonly schema: Schema.Schema readonly latestValueSubscribable: Subscribable.Subscribable> - readonly fieldLatestValueSubscribable:

>>( + useFieldLatestValueSubscribable

>>( path: P - ) => Effect.Effect, P>, NoSuchElementException>> - readonly fieldIssuesSubscribable: ( + ): Subscribable.Subscribable, P>, NoSuchElementException> + useFieldIssuesSubscribable( path: PropertyPath.Paths> - ) => Effect.Effect> + ): Subscribable.Subscribable useInput

>>( path: P @@ -48,27 +48,52 @@ extends Pipeable.Class() implements Form { readonly schema: Schema.Schema, readonly latestValueRef: SubscriptionRef.SubscriptionRef>, readonly errorRef: SubscriptionRef.SubscriptionRef>, - - readonly fieldLatestValueSubscribable:

>>( - path: P - ) => Effect.Effect, P>, NoSuchElementException>>, - readonly fieldIssuesSubscribable: ( - path: PropertyPath.Paths> - ) => Effect.Effect>, ) { super() this.latestValueSubscribable = latestValueRef } + useFieldLatestValueSubscribable

>>( + path: P + ) { + return React.useMemo(() => { + const latestValueRef = this.latestValueRef + return SubscribableInternal.make({ + get: Effect.flatMap(latestValueRef.get, PropertyPath.get(path)), + get changes() { return Stream.flatMap(latestValueRef.changes, PropertyPath.get(path)) }, + }) + }, [this.latestValueRef, ...path]) + } + + useFieldIssuesSubscribable( + path: PropertyPath.Paths> + ) { + return React.useMemo(() => { + const filter = Option.match({ + onSome: (v: ParseResult.ParseError) => Effect.andThen( + ParseResult.ArrayFormatter.formatError(v), + Array.filter(issue => PropertyPath.equivalence(issue.path, path)), + ), + onNone: () => Effect.succeed([]), + }) + + const errorRef = this.errorRef + return SubscribableInternal.make({ + get: Effect.flatMap(errorRef.get, filter), + get changes() { return Stream.flatMap(errorRef.changes, filter) }, + }) + }, [this.latestValueRef, ...path]) + } + useInput

>(path: P) { const self = this return Effect.gen(function*() { - const issuesSubscribable = yield* self.fieldIssuesSubscribable(path) const internalValueRef = yield* Hooks.useMemo(() => self.latestValueRef.pipe( Effect.andThen(Schema.encode(self.schema)), Effect.andThen(PropertyPath.get(path)), Effect.andThen(SubscriptionRef.make>), ), [self.latestValueRef, ...path]) + const issuesSubscribable = self.useFieldIssuesSubscribable(path) const [value, setValue] = yield* Hooks.useRefState(internalValueRef) const [issues] = yield* Hooks.useSubscribe(issuesSubscribable) @@ -107,31 +132,6 @@ export const make = ( options.schema, latestValueRef, errorRef, - - yield* Effect.cachedFunction( - (path: PropertyPath.PropertyPath) => Effect.succeed(SubscribableInternal.make({ - get: Effect.flatMap(latestValueRef.get, PropertyPath.get(path as PropertyPath.Paths)), - get changes() { return Stream.flatMap(latestValueRef.changes, PropertyPath.get(path as PropertyPath.Paths)) }, - })), - PropertyPath.equivalence, - ), - yield* Effect.cachedFunction( - (path: PropertyPath.PropertyPath) => Effect.gen(function*() { - const filter = Option.match({ - onSome: (v: ParseResult.ParseError) => Effect.andThen( - ParseResult.ArrayFormatter.formatError(v), - Array.filter(issue => PropertyPath.equivalence(issue.path, path)), - ), - onNone: () => Effect.succeed([]), - }) - - return SubscribableInternal.make({ - get: Effect.flatMap(errorRef.get, filter), - get changes() { return Stream.flatMap(errorRef.changes, filter) }, - }) - }), - PropertyPath.equivalence, - ), ) }) @@ -141,10 +141,3 @@ export namespace make { readonly initialValue: NoInfer } } - - -const TestSchema = Schema.Struct({ - name: Schema.String -}) -declare const form: Form -const t = form.useInput(["name"])