Form refactoring
All checks were successful
Lint / lint (push) Successful in 11s

This commit is contained in:
Julien Valverdé
2025-09-23 19:38:30 +02:00
parent ed1efb41cd
commit 9033fcbb32
2 changed files with 38 additions and 11 deletions

View File

@@ -1,4 +1,4 @@
import { Array, Duration, Effect, Equivalence, flow, identity, Option, ParseResult, Pipeable, Schema, Stream, Subscribable, SubscriptionRef } from "effect" import { Array, Duration, Effect, Equivalence, flow, identity, Option, ParseResult, Pipeable, Schema, Scope, Stream, Subscribable, SubscriptionRef } from "effect"
import type { NoSuchElementException } from "effect/Cause" import type { NoSuchElementException } from "effect/Cause"
import * as React from "react" import * as React from "react"
import { Hooks } from "./hooks/index.js" import { Hooks } from "./hooks/index.js"
@@ -53,7 +53,7 @@ extends Pipeable.Class() implements Form<A, I, R> {
export namespace make { export namespace make {
export interface Options<in out A, in out I = A, out R = never> { export interface Options<in out A, in out I, out R> {
readonly schema: Schema.Schema<A, I, R> readonly schema: Schema.Schema<A, I, R>
readonly initialEncodedValue: NoInfer<I> readonly initialEncodedValue: NoInfer<I>
} }
@@ -70,6 +70,39 @@ export const make: {
) )
}) })
const run = <A, I, R>(self: Form<A, I, R>) => Stream.runForEach(
self.encodedValueRef.changes,
flow(
Schema.decode(self.schema),
Effect.andThen(v => SubscriptionRef.set(self.valueRef, Option.some(v))),
Effect.andThen(SubscriptionRef.set(self.errorRef, Option.none())),
Effect.catchTag("ParseError", e => SubscriptionRef.set(self.errorRef, Option.some(e)))
),
)
export namespace service {
export interface Options<in out A, in out I, out R> extends make.Options<A, I, R> {}
}
export const service = <A, I = A, R = never>(
options: service.Options<A, I, R>
): Effect.Effect<Form<A, I, R>, never, R | Scope.Scope> => Effect.tap(
make(options),
form => Effect.forkScoped(run(form)),
)
export namespace useForm {
export interface Options<in out A, in out I, out R> extends make.Options<A, I, R> {}
}
export const useForm: {
<A, I = A, R = never>(options: service.Options<A, I, R>): Effect.Effect<Form<A, I, R>, never, R | Scope.Scope>
} = Effect.fnUntraced(function* <A, I = A, R = never>(options: service.Options<A, I, R>) {
const form = yield* Hooks.useOnce(() => make(options))
yield* Hooks.useFork(() => run(form), [form])
return form
})
export namespace useInput { export namespace useInput {
export interface Options { export interface Options {
@@ -114,13 +147,7 @@ export const useInput: {
internalValue => self.encodedValueRef.pipe( internalValue => self.encodedValueRef.pipe(
Effect.andThen(encodedValue => PropertyPath.immutableSet(encodedValue, path, internalValue)), Effect.andThen(encodedValue => PropertyPath.immutableSet(encodedValue, path, internalValue)),
Effect.tap(encodedValue => SubscriptionRef.set(self.encodedValueRef, encodedValue)), Effect.andThen(encodedValue => SubscriptionRef.set(self.encodedValueRef, encodedValue)),
Effect.andThen(flow(
Schema.decode(self.schema),
Effect.andThen(v => SubscriptionRef.set(self.valueRef, Option.some(v))),
Effect.andThen(SubscriptionRef.set(self.errorRef, Option.none())),
Effect.catchTag("ParseError", e => SubscriptionRef.set(self.errorRef, Option.some(e)))
)),
), ),
), [internalValueRef, self, ...path]) ), [internalValueRef, self, ...path])

View File

@@ -12,7 +12,7 @@ const RegisterFormSchema = Schema.Struct({
}) })
class RegisterForm extends Effect.Service<RegisterForm>()("RegisterForm", { class RegisterForm extends Effect.Service<RegisterForm>()("RegisterForm", {
scoped: Form.make({ scoped: Form.service({
schema: RegisterFormSchema, schema: RegisterFormSchema,
initialEncodedValue: { email: "", password: "" }, initialEncodedValue: { email: "", password: "" },
}) })
@@ -64,7 +64,7 @@ class RegisterPage extends Component.makeUntraced(function* RegisterPage() {
const RegisterRoute = Component.makeUntraced(function* RegisterRoute() { const RegisterRoute = Component.makeUntraced(function* RegisterRoute() {
const context = yield* useContext(RegisterForm.Default) const context = yield* useContext(RegisterForm.Default, { finalizerExecutionMode: "fork" })
const RegisterRouteFC = yield* Effect.provide(RegisterPage, context) const RegisterRouteFC = yield* Effect.provide(RegisterPage, context)
return <RegisterRouteFC /> return <RegisterRouteFC />