diff --git a/packages/effect-fc/src/hooks/Hooks/useInput.ts b/packages/effect-fc/src/hooks/Hooks/useInput.ts
index 19333c9..9d7a483 100644
--- a/packages/effect-fc/src/hooks/Hooks/useInput.ts
+++ b/packages/effect-fc/src/hooks/Hooks/useInput.ts
@@ -8,14 +8,14 @@ import { useRefState } from "./useRefState.js"
export namespace useInput {
export interface Options {
readonly schema: Schema.Schema
+ readonly equivalence?: Equivalence.Equivalence
readonly ref: SubscriptionRef.SubscriptionRef
readonly debounce?: Duration.DurationInput
- readonly equivalence?: Equivalence.Equivalence
}
export interface Result {
readonly value: string
- readonly onChange: React.ChangeEventHandler
+ readonly setValue: React.Dispatch>
readonly error: Option.Option
}
}
@@ -56,10 +56,8 @@ export const useInput: {
Effect.catchTag("ParseError", e => Effect.sync(() => setError(Option.some(e)))),
),
),
- ], { concurrency: "unbounded" }), [options.ref, options.schema, options.debounce, options.equivalence, internalRef])
+ ], { concurrency: "unbounded" }), [options.schema, options.equivalence, options.ref, options.debounce, internalRef])
const [value, setValue] = yield* useRefState(internalRef)
- const onChange = React.useCallback((e: React.ChangeEvent) => setValue(e.target.value), [setValue])
-
- return { value, onChange, error }
+ return { value, setValue, error }
})
diff --git a/packages/effect-fc/src/hooks/Hooks/useOptionalInput.ts b/packages/effect-fc/src/hooks/Hooks/useOptionalInput.ts
index d4977d1..837739b 100644
--- a/packages/effect-fc/src/hooks/Hooks/useOptionalInput.ts
+++ b/packages/effect-fc/src/hooks/Hooks/useOptionalInput.ts
@@ -12,16 +12,16 @@ export namespace useOptionalInput {
export interface Options {
readonly schema: Schema.Schema
readonly defaultValue?: A
+ readonly equivalence?: Equivalence.Equivalence
readonly ref: SubscriptionRef.SubscriptionRef>
readonly debounce?: Duration.DurationInput
- readonly equivalence?: Equivalence.Equivalence
}
export interface Result {
readonly value: string
- readonly onChange: React.ChangeEventHandler
- readonly disabled: boolean
- readonly setDisabled: React.Dispatch>
+ readonly setValue: React.Dispatch>
+ readonly enabled: boolean
+ readonly setEnabled: React.Dispatch>
readonly error: Option.Option
}
}
@@ -29,7 +29,7 @@ export namespace useOptionalInput {
export const useOptionalInput: {
(options: useOptionalInput.Options): Effect.Effect
} = Effect.fnUntraced(function* (options: useOptionalInput.Options) {
- const [internalRef, disabledRef] = yield* useOnce(() => Effect.andThen(options.ref, upstreamValue =>
+ const [internalRef, enabledRef] = yield* useOnce(() => Effect.andThen(options.ref, upstreamValue =>
Effect.all([
Effect.andThen(
Option.match(upstreamValue, {
@@ -41,7 +41,7 @@ export const useOptionalInput: {
SubscriptionRef.make,
),
- SubscriptionRef.make(Option.isNone(upstreamValue)),
+ SubscriptionRef.make(Option.isSome(upstreamValue)),
])
))
@@ -63,7 +63,7 @@ export const useOptionalInput: {
),
),
- onNone: () => Ref.set(disabledRef, true),
+ onNone: () => Ref.set(enabledRef, false),
})),
// Sync all changes to the internal state with upstream
@@ -76,28 +76,24 @@ export const useOptionalInput: {
Effect.catchTag("ParseError", e => Effect.sync(() => setError(Option.some(e)))),
),
),
- ], { concurrency: "unbounded" }), [options.ref, options.schema, options.debounce, options.equivalence, internalRef])
+ ], { concurrency: "unbounded" }), [options.schema, options.equivalence, options.ref, options.debounce, internalRef])
- const setDisabled = yield* useCallbackSync((setStateAction: React.SetStateAction) =>
- Effect.andThen(
- Ref.updateAndGet(disabledRef, prevState =>
- SetStateAction.value(setStateAction, prevState)
- ),
-
- disabled => disabled
- ? Ref.set(options.ref, Option.none())
- : internalRef.pipe(
+ const setEnabled = yield* useCallbackSync(
+ (setStateAction: React.SetStateAction) => Effect.andThen(
+ Ref.updateAndGet(enabledRef, prevState => SetStateAction.value(setStateAction, prevState)),
+ enabled => enabled
+ ? internalRef.pipe(
Effect.andThen(Schema.decode(options.schema)),
Effect.andThen(v => Ref.set(options.ref, Option.some(v))),
Effect.andThen(() => setError(Option.none())),
Effect.catchTag("ParseError", e => Effect.sync(() => setError(Option.some(e)))),
- ),
+ )
+ : Ref.set(options.ref, Option.none()),
),
- [disabledRef, options.ref, internalRef, options.schema])
+ [options.schema, options.ref, internalRef, enabledRef],
+ )
- const [disabled] = yield* useSubscribeRefs(disabledRef)
+ const [enabled] = yield* useSubscribeRefs(enabledRef)
const [value, setValue] = yield* useRefState(internalRef)
- const onChange = React.useCallback((e: React.ChangeEvent) => setValue(e.target.value), [setValue])
-
- return { value, onChange, disabled, setDisabled, error }
+ return { value, setValue, enabled, setEnabled, error }
})
diff --git a/packages/example/src/lib/TextAreaInput.tsx b/packages/example/src/lib/TextAreaInput.tsx
index 4893148..9bfeb37 100644
--- a/packages/example/src/lib/TextAreaInput.tsx
+++ b/packages/example/src/lib/TextAreaInput.tsx
@@ -1,45 +1,40 @@
import { Callout, Flex, TextArea, TextAreaProps } from "@radix-ui/themes"
-import { Option, ParseResult, Schema, Struct } from "effect"
+import { Array, Equivalence, Option, ParseResult, Schema, Struct } from "effect"
import { Component } from "effect-fc"
import { useInput } from "effect-fc/hooks"
import * as React from "react"
-export interface TextAreaInputProps
-extends
- Omit, "schema">,
- Omit
-{}
+export type TextAreaInputProps = Omit, "schema" | "equivalence"> & Omit
-export const TextAreaInput = (
- schema: Schema.Schema
-): Component.Component<
+export const TextAreaInput = (options: {
+ readonly schema: Schema.Schema
+ readonly equivalence?: Equivalence.Equivalence
+}): Component.Component<
TextAreaInputProps,
React.JSX.Element,
ParseResult.ParseError,
R
> => Component.makeUntraced(function* TextFieldInput(props) {
- const input = yield* useInput({ schema, ...props })
- const issues = React.useMemo(() => Option.map(
- input.error,
- ParseResult.ArrayFormatter.formatErrorSync,
+ const input = yield* useInput({ ...options, ...props })
+ const issue = React.useMemo(() => input.error.pipe(
+ Option.map(ParseResult.ArrayFormatter.formatErrorSync),
+ Option.flatMap(Array.head),
), [input.error])
return (
- {Option.isSome(issues) &&
-
-
- {issues.value.map((issue, i) => - {issue.message}
)}
-
-
- }
-
)
})
diff --git a/packages/example/src/lib/TextFieldInput.tsx b/packages/example/src/lib/TextFieldInput.tsx
index 3f941b8..0769a9e 100644
--- a/packages/example/src/lib/TextFieldInput.tsx
+++ b/packages/example/src/lib/TextFieldInput.tsx
@@ -24,8 +24,7 @@ export const TextFieldInput = (options: {
optional: true,
// eslint-disable-next-line react-hooks/rules-of-hooks
...yield* useOptionalInput({
- schema: options.schema,
- equivalence: options.equivalence,
+ ...options,
...props as TextFieldOptionalInputProps,
}),
}
@@ -33,8 +32,7 @@ export const TextFieldInput = (options: {
optional: false,
// eslint-disable-next-line react-hooks/rules-of-hooks
...yield* useInput({
- schema: options.schema,
- equivalence: options.equivalence,
+ ...options,
...props as TextFieldInputProps,
})
}
@@ -49,21 +47,20 @@ export const TextFieldInput = (options: {
{input.optional &&
input.setDisabled(!checked)}
+ checked={input.enabled}
+ onCheckedChange={checked => input.setEnabled(checked !== "indeterminate" && checked)}
/>
}
input.setValue(e.target.value)}
+ disabled={input.optional ? !input.enabled : undefined}
{...Struct.omit(props as TextFieldOptionalInputProps | TextFieldInputProps, "ref")}
/>
- {(!(input.optional && input.disabled) && Option.isSome(issue)) &&
+ {(!(input.optional && !input.enabled) && Option.isSome(issue)) &&
{issue.value.message}
diff --git a/packages/example/src/todo/Todo.tsx b/packages/example/src/todo/Todo.tsx
index 2322c98..75bf32f 100644
--- a/packages/example/src/todo/Todo.tsx
+++ b/packages/example/src/todo/Todo.tsx
@@ -12,7 +12,7 @@ import { FaDeleteLeft } from "react-icons/fa6"
import { TodosState } from "./TodosState.service"
-const StringTextAreaInput = TextAreaInput(Schema.String)
+const StringTextAreaInput = TextAreaInput({ schema: Schema.String })
const OptionalDateInput = TextFieldInput({ optional: true, schema: Schema.DateTimeUtc })
const makeTodo = makeUuid4.pipe(