Refactor Form
All checks were successful
Lint / lint (push) Successful in 14s

This commit is contained in:
Julien Valverdé
2026-03-30 16:25:16 +02:00
parent 075e92cb66
commit 0f20587827
5 changed files with 53 additions and 43 deletions

View File

@@ -1,24 +1,22 @@
import { Callout, Flex, Spinner, TextField } from "@radix-ui/themes"
import { Array, Option } from "effect"
import { Array, Option, Struct } from "effect"
import { Component, Form, Subscribable } from "effect-fc"
export declare namespace TextFieldFormInputView {
export interface Props
extends TextField.RootProps, Form.useInput.Options {
readonly field: Form.FormField<any, string>
export interface Props extends Omit<TextField.RootProps, "form">, Form.useInput.Options {
readonly form: Form.Form<readonly PropertyKey[], any, string>
}
}
export class TextFieldFormInputView extends Component.make("TextFieldFormInputView")(function*(
props: TextFieldFormInputView.Props
) {
const input = yield* Form.useInput(props.field, props)
const input = yield* Form.useInput(props.form, props)
const [issues, isValidating, isSubmitting] = yield* Subscribable.useSubscribables([
props.field.issues,
props.field.isValidating,
props.field.isSubmitting,
props.form.issues,
props.form.isValidating,
props.form.isSubmitting,
])
return (
@@ -27,7 +25,7 @@ export class TextFieldFormInputView extends Component.make("TextFieldFormInputVi
value={input.value}
onChange={e => input.setValue(e.target.value)}
disabled={isSubmitting}
{...props}
{...Struct.omit(props, "form")}
>
{isValidating &&
<TextField.Slot side="right">

View File

@@ -4,21 +4,19 @@ import { Component, Form, Subscribable } from "effect-fc"
export declare namespace TextFieldOptionalFormInputView {
export interface Props
extends Omit<TextField.RootProps, "defaultValue">, Form.useOptionalInput.Options<string> {
readonly field: Form.FormField<any, Option.Option<string>>
export interface Props extends Omit<TextField.RootProps, "form" | "defaultValue">, Form.useOptionalInput.Options<string> {
readonly form: Form.Form<readonly PropertyKey[], any, Option.Option<string>>
}
}
export class TextFieldOptionalFormInputView extends Component.make("TextFieldOptionalFormInputView")(function*(
props: TextFieldOptionalFormInputView.Props
) {
const input = yield* Form.useOptionalInput(props.field, props)
const input = yield* Form.useOptionalInput(props.form, props)
const [issues, isValidating, isSubmitting] = yield* Subscribable.useSubscribables([
props.field.issues,
props.field.isValidating,
props.field.isSubmitting,
props.form.issues,
props.form.isValidating,
props.form.isSubmitting,
])
return (
@@ -27,7 +25,7 @@ export class TextFieldOptionalFormInputView extends Component.make("TextFieldOpt
value={input.value}
onChange={e => input.setValue(e.target.value)}
disabled={!input.enabled || isSubmitting}
{...Struct.omit(props, "defaultValue")}
{...Struct.omit(props, "form", "defaultValue")}
>
<TextField.Slot side="left">
<Switch

View File

@@ -40,34 +40,42 @@ const RegisterFormSubmitSchema = Schema.Struct({
})
class RegisterFormService extends Effect.Service<RegisterFormService>()("RegisterFormService", {
scoped: Form.service({
schema: RegisterFormSchema.pipe(
Schema.compose(
Schema.transformOrFail(
Schema.typeSchema(RegisterFormSchema),
Schema.typeSchema(RegisterFormSchema),
{
decode: v => Effect.andThen(Effect.sleep("500 millis"), ParseResult.succeed(v)),
encode: ParseResult.succeed,
},
scoped: Effect.gen(function*() {
const form = yield* Form.service({
schema: RegisterFormSchema.pipe(
Schema.compose(
Schema.transformOrFail(
Schema.typeSchema(RegisterFormSchema),
Schema.typeSchema(RegisterFormSchema),
{
decode: v => Effect.andThen(Effect.sleep("500 millis"), ParseResult.succeed(v)),
encode: ParseResult.succeed,
},
),
),
),
),
initialEncodedValue: { email: "", password: "", birth: Option.none() },
f: Effect.fnUntraced(function*([value]) {
yield* Effect.sleep("500 millis")
return yield* Schema.decode(RegisterFormSubmitSchema)(value)
}),
debounce: "500 millis",
initialEncodedValue: { email: "", password: "", birth: Option.none() },
f: Effect.fnUntraced(function*([value]) {
yield* Effect.sleep("500 millis")
return yield* Schema.decode(RegisterFormSubmitSchema)(value)
}),
})
return {
form,
emailField: Form.focusObjectField(form, "email"),
passwordField: Form.focusObjectField(form, "password"),
birthField: Form.focusObjectField(form, "birth"),
} as const
})
}) {}
class RegisterFormView extends Component.make("RegisterFormView")(function*() {
const form = yield* RegisterFormService
const [canSubmit, submitResult] = yield* Subscribable.useSubscribables([
form.canSubmit,
form.mutation.result,
form.form.canSubmit,
form.form.mutation.result,
])
const runPromise = yield* Component.useRunPromise()
@@ -84,12 +92,10 @@ class RegisterFormView extends Component.make("RegisterFormView")(function*() {
<Container width="300">
<form onSubmit={e => {
e.preventDefault()
void runPromise(form.submit)
void runPromise(form.form.submit)
}}>
<Flex direction="column" gap="2">
<TextFieldFormInput
field={yield* form.field(["email"])}
/>
<TextFieldFormInput form={form.emailField} />
<TextFieldFormInput
field={yield* form.field(["password"])}