0.1.4 #5

Merged
Thilawyn merged 67 commits from next into master 2025-10-02 18:18:23 +02:00
2 changed files with 38 additions and 24 deletions
Showing only changes of commit 450d11cc3e - Show all commits

View File

@@ -21,7 +21,7 @@ extends Pipeable.Pipeable {
): Subscribable.Subscribable<readonly ParseResult.ArrayFormatterIssue[]> ): Subscribable.Subscribable<readonly ParseResult.ArrayFormatterIssue[]>
useInput<P extends PropertyPath.Paths<I>>( useInput<P extends PropertyPath.Paths<I>>(
path: P options: Form.useInput.Options<I, P>
): Effect.Effect< ): Effect.Effect<
Form.useInput.Result<PropertyPath.ValueFromPath<I, P>>, Form.useInput.Result<PropertyPath.ValueFromPath<I, P>>,
ParseResult.ParseError | NoSuchElementException, ParseResult.ParseError | NoSuchElementException,
@@ -31,9 +31,14 @@ extends Pipeable.Pipeable {
export namespace Form { export namespace Form {
export namespace useInput { export namespace useInput {
export interface Result<I> { export interface Options<I, P extends PropertyPath.Paths<I>> {
readonly value: I readonly path: P
readonly setValue: React.Dispatch<React.SetStateAction<I>> readonly defaultValue?: PropertyPath.ValueFromPath<NoInfer<I>, NoInfer<P>>
}
export interface Result<T> {
readonly value: T
readonly setValue: React.Dispatch<React.SetStateAction<T>>
readonly issues: readonly ParseResult.ArrayFormatterIssue[] readonly issues: readonly ParseResult.ArrayFormatterIssue[]
} }
} }
@@ -86,15 +91,24 @@ extends Pipeable.Class() implements Form<A, I, R> {
}, [this.latestValueRef, ...path]) }, [this.latestValueRef, ...path])
} }
useInput<P extends PropertyPath.Paths<I>>(path: P) { useInput<P extends PropertyPath.Paths<I>>(
options: Form.useInput.Options<I, P>
) {
const self = this const self = this
return Effect.gen(function*() { return Effect.gen(function*() {
const internalValueRef = yield* Hooks.useMemo(() => self.latestValueRef.pipe( const internalValueRef = yield* Hooks.useMemo(() => self.latestValueRef.pipe(
Effect.andThen(Schema.encode(self.schema)), Effect.andThen(flow(
Effect.andThen(PropertyPath.get(path)), Schema.encode(self.schema),
Effect.andThen(PropertyPath.get(options.path)),
Effect.catchTag("ParseError", e => options.defaultValue !== undefined && options.defaultValue !== null
? Effect.succeed(options.defaultValue)
: Effect.fail(e)
),
)),
Effect.andThen(SubscriptionRef.make<PropertyPath.ValueFromPath<I, P>>), Effect.andThen(SubscriptionRef.make<PropertyPath.ValueFromPath<I, P>>),
), [self.latestValueRef, ...path]) ), [self.latestValueRef, ...options.path])
const issuesSubscribable = self.useFieldIssuesSubscribable(path)
const issuesSubscribable = self.useFieldIssuesSubscribable(options.path)
const [value, setValue] = yield* Hooks.useRefState(internalValueRef) const [value, setValue] = yield* Hooks.useRefState(internalValueRef)
const [issues] = yield* Hooks.useSubscribe(issuesSubscribable) const [issues] = yield* Hooks.useSubscribe(issuesSubscribable)
@@ -107,7 +121,7 @@ extends Pipeable.Class() implements Form<A, I, R> {
), ),
internalValue => self.latestValueRef.pipe( internalValue => self.latestValueRef.pipe(
Effect.andThen(Schema.encode(self.schema)), Effect.andThen(Schema.encode(self.schema)),
Effect.andThen(PropertyPath.immutableSet(path, internalValue)), Effect.andThen(PropertyPath.immutableSet(options.path, internalValue)),
Effect.andThen(flow( Effect.andThen(flow(
Schema.decode(self.schema), Schema.decode(self.schema),
Effect.andThen(v => SubscriptionRef.set(self.latestValueRef, v)), Effect.andThen(v => SubscriptionRef.set(self.latestValueRef, v)),
@@ -115,7 +129,7 @@ extends Pipeable.Class() implements Form<A, I, R> {
Effect.catchTag("ParseError", e => SubscriptionRef.set(self.errorRef, Option.some(e))) Effect.catchTag("ParseError", e => SubscriptionRef.set(self.errorRef, Option.some(e)))
)), )),
), ),
), [internalValueRef, self.latestValueRef, self.schema, self.errorRef, ...path]) ), [internalValueRef, self.latestValueRef, self.schema, self.errorRef, ...options.path])
return { value, setValue, issues } return { value, setValue, issues }
}) })

View File

@@ -6,22 +6,22 @@ import { Component, Form } from "effect-fc"
import { useContext, useFork } from "effect-fc/hooks" import { useContext, useFork } from "effect-fc/hooks"
const LoginFormSchema = Schema.Struct({ const RegisterFormSchema = Schema.Struct({
email: Schema.String, email: Schema.String,
password: Schema.String.pipe(Schema.minLength(3)), password: Schema.String.pipe(Schema.minLength(3)),
}) })
class LoginForm extends Effect.Service<LoginForm>()("LoginForm", { class RegisterForm extends Effect.Service<RegisterForm>()("RegisterForm", {
scoped: Form.make({ scoped: Form.make({
schema: LoginFormSchema, schema: RegisterFormSchema,
initialValue: { email: "", password: "xxx" }, initialValue: { email: "", password: "" },
}) })
}) {} }) {}
class LoginFormComponent extends Component.makeUntraced(function* LoginFormComponent() { class RegisterPage extends Component.makeUntraced(function* RegisterPage() {
const form = yield* LoginForm const form = yield* RegisterForm
const emailInput = yield* form.useInput(["email"]) const emailInput = yield* form.useInput({ path: ["email"], defaultValue: "" })
const passwordInput = yield* form.useInput(["password"]) const passwordInput = yield* form.useInput({ path: ["password"], defaultValue: "" })
yield* useFork(() => Stream.runForEach(form.latestValueSubscribable.changes, Console.log), []) yield* useFork(() => Stream.runForEach(form.latestValueSubscribable.changes, Console.log), [])
@@ -63,15 +63,15 @@ class LoginFormComponent extends Component.makeUntraced(function* LoginFormCompo
}) {} }) {}
const FormRoute = Component.makeUntraced(function* FormRoute() { const RegisterRoute = Component.makeUntraced(function* RegisterRoute() {
const context = yield* useContext(LoginForm.Default) const context = yield* useContext(RegisterForm.Default)
const LoginFormComponentFC = yield* Effect.provide(LoginFormComponent, context) const RegisterRouteFC = yield* Effect.provide(RegisterPage, context)
return <LoginFormComponentFC /> return <RegisterRouteFC />
}).pipe( }).pipe(
Component.withRuntime(runtime.context) Component.withRuntime(runtime.context)
) )
export const Route = createFileRoute("/form")({ export const Route = createFileRoute("/form")({
component: FormRoute component: RegisterRoute
}) })