diff --git a/packages/effect-fc/src/hooks/Hooks/useInput.ts b/packages/effect-fc/src/hooks/Hooks/useInput.ts
index e5444ea..d975edc 100644
--- a/packages/effect-fc/src/hooks/Hooks/useInput.ts
+++ b/packages/effect-fc/src/hooks/Hooks/useInput.ts
@@ -1,4 +1,4 @@
-import { type Duration, Effect, Equal, flow, identity, Option, type ParseResult, Ref, Schema, Stream, SubscriptionRef } from "effect"
+import { type Duration, Effect, Equal, type Equivalence, flow, identity, Option, type ParseResult, Ref, Schema, Stream, SubscriptionRef } from "effect"
import * as React from "react"
import { useFork } from "./useFork.js"
import { useOnce } from "./useOnce.js"
@@ -10,6 +10,7 @@ export namespace useInput {
readonly schema: Schema.Schema
readonly ref: SubscriptionRef.SubscriptionRef
readonly debounce?: Duration.DurationInput
+ readonly equivalence?: Equivalence.Equivalence
}
export interface Result {
@@ -30,7 +31,7 @@ export const useInput: {
yield* useFork(() => Effect.all([
// Sync the upstream state with the internal state
- // Only mutate the internal state if the upstream encoded value is actually different. This avoids infinite re-render loops.
+ // Only mutate the internal state if the upstream value is actually different. This avoids infinite re-render loops.
Stream.runForEach(options.ref.changes, upstreamValue =>
Effect.whenEffect(
Effect.andThen(
@@ -39,7 +40,7 @@ export const useInput: {
),
internalRef.pipe(
Effect.andThen(Schema.decode(options.schema)),
- Effect.andThen(decodedInternalValue => !Equal.equals(upstreamValue, decodedInternalValue)),
+ Effect.andThen(decodedInternalValue => !(options.equivalence ?? Equal.equals)(upstreamValue, decodedInternalValue)),
),
)
),
@@ -54,7 +55,7 @@ export const useInput: {
Effect.catchTag("ParseError", e => Effect.sync(() => setError(Option.some(e)))),
),
),
- ], { concurrency: "unbounded" }), [options.ref, options.schema, options.debounce, internalRef])
+ ], { concurrency: "unbounded" }), [options.ref, options.schema, options.debounce, options.equivalence, internalRef])
const [value, setValue] = yield* useRefState(internalRef)
const onChange = React.useCallback((e: React.ChangeEvent) => setValue(e.target.value), [setValue])
diff --git a/packages/effect-fc/src/hooks/Hooks/useOptionalInput.ts b/packages/effect-fc/src/hooks/Hooks/useOptionalInput.ts
index 320a58e..ea04ee1 100644
--- a/packages/effect-fc/src/hooks/Hooks/useOptionalInput.ts
+++ b/packages/effect-fc/src/hooks/Hooks/useOptionalInput.ts
@@ -1,9 +1,10 @@
-import { type Duration, Effect, flow, identity, Option, type ParseResult, Ref, Schema, Stream, SubscriptionRef } from "effect"
+import { type Duration, Effect, Equal, type Equivalence, flow, identity, Option, type ParseResult, Ref, Schema, Stream, SubscriptionRef } from "effect"
import * as React from "react"
import { SetStateAction } from "../../types/index.js"
import { useCallbackSync } from "./useCallbackSync.js"
import { useFork } from "./useFork.js"
import { useOnce } from "./useOnce.js"
+import { useRefState } from "./useRefState.js"
import { useSubscribeRefs } from "./useSubscribeRefs.js"
@@ -13,6 +14,7 @@ export namespace useOptionalInput {
readonly defaultValue?: A
readonly ref: SubscriptionRef.SubscriptionRef>
readonly debounce?: Duration.DurationInput
+ readonly equivalence?: Equivalence.Equivalence
}
export interface Result {
@@ -49,14 +51,14 @@ export const useOptionalInput: {
// Sync the upstream state with the internal state
// Only mutate the internal state if the upstream encoded value is actually different. This avoids infinite re-render loops.
Stream.runForEach(options.ref.changes, Option.match({
- onSome: upstreamValue => Effect.andThen(
- Effect.all([Schema.encode(options.schema)(upstreamValue), internalRef]),
- ([encodedUpstreamValue, internalValue]) => Effect.when(
- Effect.andThen(
- Ref.set(internalRef, encodedUpstreamValue),
- Ref.set(disabledRef, false),
- ),
- () => encodedUpstreamValue !== internalValue,
+ onSome: upstreamValue => Effect.whenEffect(
+ Effect.andThen(
+ Schema.encode(options.schema)(upstreamValue),
+ encodedUpstreamValue => Ref.set(internalRef, encodedUpstreamValue),
+ ),
+ internalRef.pipe(
+ Effect.andThen(Schema.decode(options.schema)),
+ Effect.andThen(decodedInternalValue => !(options.equivalence ?? Equal.equals)(upstreamValue, decodedInternalValue)),
),
),
@@ -73,13 +75,7 @@ export const useOptionalInput: {
Effect.catchTag("ParseError", e => Effect.sync(() => setError(Option.some(e)))),
),
),
- ], { concurrency: "unbounded" }), [options.ref, options.schema, options.debounce, internalRef])
-
- const [value, disabled] = yield* useSubscribeRefs(internalRef, disabledRef)
- const onChange = yield* useCallbackSync((e: React.ChangeEvent) => Ref.set(
- internalRef,
- e.target.value,
- ), [internalRef])
+ ], { concurrency: "unbounded" }), [options.ref, options.schema, options.debounce, options.equivalence, internalRef])
const setDisabled = yield* useCallbackSync((setStateAction: React.SetStateAction) =>
Effect.andThen(
@@ -98,5 +94,9 @@ export const useOptionalInput: {
),
[disabledRef, options.ref, internalRef, options.schema])
+ const [disabled] = yield* useSubscribeRefs(disabledRef)
+ const [value, setValue] = yield* useRefState(internalRef)
+ const onChange = React.useCallback((e: React.ChangeEvent) => setValue(e.target.value), [setValue])
+
return { value, onChange, disabled, setDisabled, error }
})
diff --git a/packages/example/src/routes/dev/input.tsx b/packages/example/src/routes/dev/input.tsx
index 7946fe0..a2b7dc9 100644
--- a/packages/example/src/routes/dev/input.tsx
+++ b/packages/example/src/routes/dev/input.tsx
@@ -29,7 +29,7 @@ const Input = Component.makeUntraced(function* Input() {
- setStr(e.target.value)} />
+
)
}).pipe(