This commit is contained in:
@@ -260,7 +260,6 @@ export namespace useInput {
|
|||||||
export interface Result<T> {
|
export interface Result<T> {
|
||||||
readonly value: T
|
readonly value: T
|
||||||
readonly setValue: React.Dispatch<React.SetStateAction<T>>
|
readonly setValue: React.Dispatch<React.SetStateAction<T>>
|
||||||
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 internalValueRef = yield* Hooks.useMemo(() => Effect.andThen(field.encodedValueRef, SubscriptionRef.make), [field])
|
||||||
const [value, setValue] = yield* Hooks.useRefState(internalValueRef)
|
const [value, setValue] = yield* Hooks.useRefState(internalValueRef)
|
||||||
const [issues] = yield* Hooks.useSubscribables(field.issuesSubscribable)
|
|
||||||
|
|
||||||
yield* Hooks.useFork(() => Effect.all([
|
yield* Hooks.useFork(() => Effect.all([
|
||||||
Stream.runForEach(
|
Stream.runForEach(
|
||||||
@@ -296,7 +294,7 @@ export const useInput: {
|
|||||||
),
|
),
|
||||||
], { concurrency: "unbounded" }), [field, internalValueRef, options?.debounce])
|
], { concurrency: "unbounded" }), [field, internalValueRef, options?.debounce])
|
||||||
|
|
||||||
return { value, setValue, issues }
|
return { value, setValue }
|
||||||
})
|
})
|
||||||
|
|
||||||
export namespace useOptionalInput {
|
export namespace useOptionalInput {
|
||||||
@@ -329,7 +327,6 @@ export const useOptionalInput: {
|
|||||||
|
|
||||||
const [enabled, setEnabled] = yield* Hooks.useRefState(enabledRef)
|
const [enabled, setEnabled] = yield* Hooks.useRefState(enabledRef)
|
||||||
const [value, setValue] = yield* Hooks.useRefState(internalValueRef)
|
const [value, setValue] = yield* Hooks.useRefState(internalValueRef)
|
||||||
const [issues] = yield* Hooks.useSubscribables(field.issuesSubscribable)
|
|
||||||
|
|
||||||
yield* Hooks.useFork(() => Effect.all([
|
yield* Hooks.useFork(() => Effect.all([
|
||||||
Stream.runForEach(
|
Stream.runForEach(
|
||||||
@@ -365,5 +362,5 @@ export const useOptionalInput: {
|
|||||||
),
|
),
|
||||||
], { concurrency: "unbounded" }), [field, enabledRef, internalValueRef, options.debounce])
|
], { concurrency: "unbounded" }), [field, enabledRef, internalValueRef, options.debounce])
|
||||||
|
|
||||||
return { enabled, setEnabled, value, setValue, issues }
|
return { enabled, setEnabled, value, setValue }
|
||||||
})
|
})
|
||||||
|
|||||||
40
packages/example/src/lib/form/TextFieldFormInput.tsx
Normal file
40
packages/example/src/lib/form/TextFieldFormInput.tsx
Normal file
@@ -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<any, string>
|
||||||
|
}
|
||||||
|
|
||||||
|
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 (
|
||||||
|
<Flex direction="column" gap="1">
|
||||||
|
<TextField.Root
|
||||||
|
value={value}
|
||||||
|
onChange={e => setValue(e.target.value)}
|
||||||
|
disabled={isSubmitting}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{Option.match(Array.head(issues), {
|
||||||
|
onSome: issue => (
|
||||||
|
<Callout.Root>
|
||||||
|
<Callout.Text>{issue.message}</Callout.Text>
|
||||||
|
</Callout.Root>
|
||||||
|
),
|
||||||
|
|
||||||
|
onNone: () => <></>,
|
||||||
|
})}
|
||||||
|
</Flex>
|
||||||
|
)
|
||||||
|
}) {}
|
||||||
@@ -1,7 +1,8 @@
|
|||||||
|
import { TextFieldFormInput } from "@/lib/form/TextFieldFormInput"
|
||||||
import { runtime } from "@/runtime"
|
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 { createFileRoute } from "@tanstack/react-router"
|
||||||
import { Array, Effect, Option, Schema } from "effect"
|
import { Effect, Schema } from "effect"
|
||||||
import { Component, Form } from "effect-fc"
|
import { Component, Form } from "effect-fc"
|
||||||
import { useContext, useSubscribables } from "effect-fc/hooks"
|
import { useContext, useSubscribables } from "effect-fc/hooks"
|
||||||
|
|
||||||
@@ -34,14 +35,11 @@ class RegisterForm extends Effect.Service<RegisterForm>()("RegisterForm", {
|
|||||||
|
|
||||||
class RegisterPage extends Component.makeUntraced("RegisterPage")(function*() {
|
class RegisterPage extends Component.makeUntraced("RegisterPage")(function*() {
|
||||||
const form = yield* RegisterForm
|
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 submit = yield* Form.useSubmit(form)
|
||||||
const [canSubmit] = yield* useSubscribables(form.canSubmitSubscribable)
|
const [canSubmit] = yield* useSubscribables(form.canSubmitSubscribable)
|
||||||
|
|
||||||
|
const TextFieldFormInputFC = yield* TextFieldFormInput
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Container>
|
<Container>
|
||||||
<form onSubmit={e => {
|
<form onSubmit={e => {
|
||||||
@@ -49,36 +47,16 @@ class RegisterPage extends Component.makeUntraced("RegisterPage")(function*() {
|
|||||||
void submit()
|
void submit()
|
||||||
}}>
|
}}>
|
||||||
<Flex direction="column" gap="2">
|
<Flex direction="column" gap="2">
|
||||||
<TextField.Root
|
<TextFieldFormInputFC
|
||||||
value={emailInput.value}
|
field={Form.useField(form, ["email"])}
|
||||||
onChange={e => emailInput.setValue(e.target.value)}
|
debounce="500 millis"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{Option.match(Array.head(emailInput.issues), {
|
<TextFieldFormInputFC
|
||||||
onSome: issue => (
|
field={Form.useField(form, ["password"])}
|
||||||
<Callout.Root>
|
debounce="500 millis"
|
||||||
<Callout.Text>{issue.message}</Callout.Text>
|
|
||||||
</Callout.Root>
|
|
||||||
),
|
|
||||||
|
|
||||||
onNone: () => <></>,
|
|
||||||
})}
|
|
||||||
|
|
||||||
<TextField.Root
|
|
||||||
value={passwordInput.value}
|
|
||||||
onChange={e => passwordInput.setValue(e.target.value)}
|
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{Option.match(Array.head(passwordInput.issues), {
|
|
||||||
onSome: issue => (
|
|
||||||
<Callout.Root>
|
|
||||||
<Callout.Text>{issue.message}</Callout.Text>
|
|
||||||
</Callout.Root>
|
|
||||||
),
|
|
||||||
|
|
||||||
onNone: () => <></>,
|
|
||||||
})}
|
|
||||||
|
|
||||||
<Button disabled={!canSubmit}>Submit</Button>
|
<Button disabled={!canSubmit}>Submit</Button>
|
||||||
</Flex>
|
</Flex>
|
||||||
</form>
|
</form>
|
||||||
|
|||||||
Reference in New Issue
Block a user