From 040e671fd3d82f23c096f22f1a081ffae0dbc223 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Valverd=C3=A9?= Date: Tue, 30 Sep 2025 15:54:59 +0200 Subject: [PATCH] Working field --- packages/effect-fc/src/Form.ts | 7 +-- .../src/lib/form/TextFieldFormInput.tsx | 40 +++++++++++++++++ packages/example/src/routes/form.tsx | 44 +++++-------------- 3 files changed, 53 insertions(+), 38 deletions(-) create mode 100644 packages/example/src/lib/form/TextFieldFormInput.tsx diff --git a/packages/effect-fc/src/Form.ts b/packages/effect-fc/src/Form.ts index 10ff4f6..e108139 100644 --- a/packages/effect-fc/src/Form.ts +++ b/packages/effect-fc/src/Form.ts @@ -260,7 +260,6 @@ export namespace useInput { export interface Result { readonly value: T readonly setValue: React.Dispatch> - readonly issues: readonly ParseResult.ArrayFormatterIssue[] } } @@ -275,7 +274,6 @@ export const useInput: { ) { const internalValueRef = yield* Hooks.useMemo(() => Effect.andThen(field.encodedValueRef, SubscriptionRef.make), [field]) const [value, setValue] = yield* Hooks.useRefState(internalValueRef) - const [issues] = yield* Hooks.useSubscribables(field.issuesSubscribable) yield* Hooks.useFork(() => Effect.all([ Stream.runForEach( @@ -296,7 +294,7 @@ export const useInput: { ), ], { concurrency: "unbounded" }), [field, internalValueRef, options?.debounce]) - return { value, setValue, issues } + return { value, setValue } }) export namespace useOptionalInput { @@ -329,7 +327,6 @@ export const useOptionalInput: { const [enabled, setEnabled] = yield* Hooks.useRefState(enabledRef) const [value, setValue] = yield* Hooks.useRefState(internalValueRef) - const [issues] = yield* Hooks.useSubscribables(field.issuesSubscribable) yield* Hooks.useFork(() => Effect.all([ Stream.runForEach( @@ -365,5 +362,5 @@ export const useOptionalInput: { ), ], { concurrency: "unbounded" }), [field, enabledRef, internalValueRef, options.debounce]) - return { enabled, setEnabled, value, setValue, issues } + return { enabled, setEnabled, value, setValue } }) diff --git a/packages/example/src/lib/form/TextFieldFormInput.tsx b/packages/example/src/lib/form/TextFieldFormInput.tsx new file mode 100644 index 0000000..ea596b0 --- /dev/null +++ b/packages/example/src/lib/form/TextFieldFormInput.tsx @@ -0,0 +1,40 @@ +import { Callout, Flex, TextField } from "@radix-ui/themes" +import { Array, Option } from "effect" +import { Component, Form } from "effect-fc" +import { useSubscribables } from "effect-fc/hooks" + + +export interface TextFieldFormInputProps +extends TextField.RootProps, Form.useInput.Options { + readonly field: Form.FormField +} + +export class TextFieldFormInput extends Component.makeUntraced("TextFieldFormInput")(function*(props: TextFieldFormInputProps) { + const { value, setValue } = yield* Form.useInput(props.field, props) + const [issues, isValidating, isSubmitting] = yield* useSubscribables( + props.field.issuesSubscribable, + props.field.isValidatingSubscribable, + props.field.isSubmittingSubscribable, + ) + + return ( + + setValue(e.target.value)} + disabled={isSubmitting} + {...props} + /> + + {Option.match(Array.head(issues), { + onSome: issue => ( + + {issue.message} + + ), + + onNone: () => <>, + })} + + ) +}) {} diff --git a/packages/example/src/routes/form.tsx b/packages/example/src/routes/form.tsx index ddb6942..4f7f831 100644 --- a/packages/example/src/routes/form.tsx +++ b/packages/example/src/routes/form.tsx @@ -1,7 +1,8 @@ +import { TextFieldFormInput } from "@/lib/form/TextFieldFormInput" import { runtime } from "@/runtime" -import { Button, Callout, Container, Flex, TextField } from "@radix-ui/themes" +import { Button, Container, Flex } from "@radix-ui/themes" import { createFileRoute } from "@tanstack/react-router" -import { Array, Effect, Option, Schema } from "effect" +import { Effect, Schema } from "effect" import { Component, Form } from "effect-fc" import { useContext, useSubscribables } from "effect-fc/hooks" @@ -34,14 +35,11 @@ class RegisterForm extends Effect.Service()("RegisterForm", { class RegisterPage extends Component.makeUntraced("RegisterPage")(function*() { const form = yield* RegisterForm - const emailField = Form.useField(form, ["email"]) - const passwordField = Form.useField(form, ["password"]) - const emailInput = yield* Form.useInput(emailField, { debounce: "200 millis" }) - const passwordInput = yield* Form.useInput(passwordField, { debounce: "200 millis" }) - const submit = yield* Form.useSubmit(form) const [canSubmit] = yield* useSubscribables(form.canSubmitSubscribable) + const TextFieldFormInputFC = yield* TextFieldFormInput + return (
{ @@ -49,36 +47,16 @@ class RegisterPage extends Component.makeUntraced("RegisterPage")(function*() { void submit() }}> - emailInput.setValue(e.target.value)} + - {Option.match(Array.head(emailInput.issues), { - onSome: issue => ( - - {issue.message} - - ), - - onNone: () => <>, - })} - - passwordInput.setValue(e.target.value)} + - {Option.match(Array.head(passwordInput.issues), { - onSome: issue => ( - - {issue.message} - - ), - - onNone: () => <>, - })} -