diff --git a/packages/effect-fc/src/Form.ts b/packages/effect-fc/src/Form.ts
index 23731cd..0ccac23 100644
--- a/packages/effect-fc/src/Form.ts
+++ b/packages/effect-fc/src/Form.ts
@@ -18,6 +18,7 @@ extends Pipeable.Pipeable {
readonly schema: Schema.Schema
readonly onSubmit: (value: NoInfer) => Effect.Effect
+ readonly autosubmit: Option.Option
readonly debounce: Option.Option
readonly valueRef: SubscriptionRef.SubscriptionRef>
@@ -36,6 +37,7 @@ extends Pipeable.Class() implements Form {
constructor(
readonly schema: Schema.Schema,
readonly onSubmit: (value: NoInfer) => Effect.Effect,
+ readonly autosubmit: Option.Option,
readonly debounce: Option.Option,
readonly valueRef: SubscriptionRef.SubscriptionRef>,
@@ -53,11 +55,15 @@ extends Pipeable.Class() implements Form {
export const isForm = (u: unknown): u is Form => Predicate.hasProperty(u, FormTypeId)
export namespace make {
- export interface Options {
+ export interface Options {
readonly schema: Schema.Schema
readonly initialEncodedValue: NoInfer
- readonly onSubmit: (value: NoInfer) => Effect.Effect,
- readonly debounce?: Duration.DurationInput,
+ readonly onSubmit: (
+ this: Form, NoInfer, NoInfer, unknown, unknown, unknown>,
+ value: NoInfer,
+ ) => Effect.Effect
+ readonly autosubmit?: boolean
+ readonly debounce?: Duration.DurationInput
}
}
@@ -76,6 +82,7 @@ export const make: {
return new FormImpl(
options.schema,
options.onSubmit,
+ Option.fromNullable(options.autosubmit),
Option.fromNullable(options.debounce),
valueRef,
@@ -114,9 +121,9 @@ export const run = (
Effect.exit,
Effect.andThen(flow(
Exit.matchEffect({
- onSuccess: v => Effect.andThen(
- SubscriptionRef.set(self.valueRef, Option.some(v)),
- SubscriptionRef.set(self.errorRef, Option.none()),
+ onSuccess: v => SubscriptionRef.set(self.valueRef, Option.some(v)).pipe(
+ Effect.andThen(SubscriptionRef.set(self.errorRef, Option.none())),
+ Effect.as(Option.some(v)),
),
onFailure: c => Option.match(
Chunk.findFirst(Cause.failures(c), e => e._tag === "ParseError"),
@@ -158,7 +165,7 @@ export const submit = (
)
export namespace service {
- export interface Options
+ export interface Options
extends make.Options {}
}
diff --git a/packages/example/src/todo/Todo.tsx b/packages/example/src/todo/Todo.tsx
index 2fb685f..f01304b 100644
--- a/packages/example/src/todo/Todo.tsx
+++ b/packages/example/src/todo/Todo.tsx
@@ -5,10 +5,16 @@ import { Component, Form, Hooks, Memoized, Subscribable, SubscriptionSubRef } fr
import { FaArrowDown, FaArrowUp } from "react-icons/fa"
import { FaDeleteLeft } from "react-icons/fa6"
import * as Domain from "@/domain"
+import { TextFieldFormInput } from "@/lib/form/TextFieldFormInput"
import { DateTimeUtcFromZonedInput } from "@/lib/schema"
import { TodosState } from "./TodosState.service"
+const TodoFormSchema = Schema.compose(Schema.Struct({
+ ...Domain.Todo.Todo.fields,
+ completedAt: Schema.OptionFromSelf(DateTimeUtcFromZonedInput),
+}), Domain.Todo.Todo)
+
const makeTodo = makeUuid4.pipe(
Effect.map(id => Domain.Todo.Todo.make({
id,
@@ -28,51 +34,62 @@ export class Todo extends Component.makeUntraced("Todo")(function*(props: TodoPr
const runtime = yield* Effect.runtime()
const state = yield* TodosState
- const { ref, indexRef, contentRef, completedAtRef } = yield* Hooks.useMemo(() => Match.value(props).pipe(
- Match.tag("new", () => Effect.Do.pipe(
- Effect.bind("ref", () => Effect.andThen(makeTodo, SubscriptionRef.make)),
- Effect.let("indexRef", () => Subscribable.make({ get: Effect.succeed(-1), changes: Stream.empty })),
- )),
- Match.tag("edit", ({ id }) => Effect.Do.pipe(
- Effect.let("ref", () => state.getElementRef(id)),
- Effect.let("indexRef", () => state.getIndexSubscribable(id)),
- )),
- Match.exhaustive,
+ const [indexRef, form, contentField, completedAtField] = yield* Component.useOnChange(() => Effect.gen(function*() {
+ const indexRef = Match.value(props).pipe(
+ Match.tag("new", () => Subscribable.make({ get: Effect.succeed(-1), changes: Stream.empty })),
+ Match.tag("edit", ({ id }) => state.getIndexSubscribable(id)),
+ Match.exhaustive,
+ )
- Effect.let("contentRef", ({ ref }) => SubscriptionSubRef.makeFromPath(ref, ["content"])),
- Effect.let("completedAtRef", ({ ref }) => SubscriptionSubRef.makeFromPath(ref, ["completedAt"])),
- ), [props._tag, props._tag === "edit" ? props.id : undefined])
-
- const { form } = yield* Component.useOnChange(() => Effect.gen(function*() {
const form = yield* Form.service({
- schema: Domain.Todo.TodoFromJson,
- initialEncodedValue: yield* Schema.encode(Domain.Todo.TodoFromJson)(
+ schema: TodoFormSchema,
+ initialEncodedValue: yield* Schema.encode(TodoFormSchema)(
yield* Match.value(props).pipe(
Match.tag("new", () => makeTodo),
Match.tag("edit", ({ id }) => state.getElementRef(id)),
Match.exhaustive,
)
),
- onSubmit: v => Effect.void,
+ onSubmit: function(todo) {
+ return Match.value(props).pipe(
+ Match.tag("new", () => Ref.update(state.ref, Chunk.prepend(todo)).pipe(
+ Effect.andThen(makeTodo),
+ Effect.andThen(Schema.encode(TodoFormSchema)),
+ Effect.andThen(v => Ref.set(this.encodedValueRef, v)),
+ )),
+ Match.tag("edit", ({ id }) => Ref.set(state.getElementRef(id), todo)),
+ Match.exhaustive,
+ )
+ },
})
- return { form }
+ return [
+ indexRef,
+ form,
+ Form.field(form, ["content"]),
+ Form.field(form, ["completedAt"]),
+ ] as const
}), [props._tag, props._tag === "edit" ? props.id : undefined])
const [index, size] = yield* Hooks.useSubscribables(indexRef, state.sizeSubscribable)
+ const submit = yield* Form.useSubmit(form)
+ const TextFieldFormInputFC = yield* TextFieldFormInput
return (
-
+
- DateTime.now)}
+ defaultValue=""
/>
{props._tag === "new" &&