@@ -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 * as React from "react"
|
||||||
import { useFork } from "./useFork.js"
|
import { useFork } from "./useFork.js"
|
||||||
import { useOnce } from "./useOnce.js"
|
import { useOnce } from "./useOnce.js"
|
||||||
@@ -10,6 +10,7 @@ export namespace useInput {
|
|||||||
readonly schema: Schema.Schema<A, string, R>
|
readonly schema: Schema.Schema<A, string, R>
|
||||||
readonly ref: SubscriptionRef.SubscriptionRef<A>
|
readonly ref: SubscriptionRef.SubscriptionRef<A>
|
||||||
readonly debounce?: Duration.DurationInput
|
readonly debounce?: Duration.DurationInput
|
||||||
|
readonly equivalence?: Equivalence.Equivalence<A>
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Result {
|
export interface Result {
|
||||||
@@ -30,7 +31,7 @@ export const useInput: {
|
|||||||
|
|
||||||
yield* useFork(() => Effect.all([
|
yield* useFork(() => Effect.all([
|
||||||
// Sync the upstream state with the internal state
|
// 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 =>
|
Stream.runForEach(options.ref.changes, upstreamValue =>
|
||||||
Effect.whenEffect(
|
Effect.whenEffect(
|
||||||
Effect.andThen(
|
Effect.andThen(
|
||||||
@@ -39,7 +40,7 @@ export const useInput: {
|
|||||||
),
|
),
|
||||||
internalRef.pipe(
|
internalRef.pipe(
|
||||||
Effect.andThen(Schema.decode(options.schema)),
|
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)))),
|
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 [value, setValue] = yield* useRefState(internalRef)
|
||||||
const onChange = React.useCallback((e: React.ChangeEvent<HTMLInputElement>) => setValue(e.target.value), [setValue])
|
const onChange = React.useCallback((e: React.ChangeEvent<HTMLInputElement>) => setValue(e.target.value), [setValue])
|
||||||
|
|||||||
@@ -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 * as React from "react"
|
||||||
import { SetStateAction } from "../../types/index.js"
|
import { SetStateAction } from "../../types/index.js"
|
||||||
import { useCallbackSync } from "./useCallbackSync.js"
|
import { useCallbackSync } from "./useCallbackSync.js"
|
||||||
import { useFork } from "./useFork.js"
|
import { useFork } from "./useFork.js"
|
||||||
import { useOnce } from "./useOnce.js"
|
import { useOnce } from "./useOnce.js"
|
||||||
|
import { useRefState } from "./useRefState.js"
|
||||||
import { useSubscribeRefs } from "./useSubscribeRefs.js"
|
import { useSubscribeRefs } from "./useSubscribeRefs.js"
|
||||||
|
|
||||||
|
|
||||||
@@ -13,6 +14,7 @@ export namespace useOptionalInput {
|
|||||||
readonly defaultValue?: A
|
readonly defaultValue?: A
|
||||||
readonly ref: SubscriptionRef.SubscriptionRef<Option.Option<A>>
|
readonly ref: SubscriptionRef.SubscriptionRef<Option.Option<A>>
|
||||||
readonly debounce?: Duration.DurationInput
|
readonly debounce?: Duration.DurationInput
|
||||||
|
readonly equivalence?: Equivalence.Equivalence<A>
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Result {
|
export interface Result {
|
||||||
@@ -49,14 +51,14 @@ export const useOptionalInput: {
|
|||||||
// Sync the upstream state with the internal state
|
// 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 encoded value is actually different. This avoids infinite re-render loops.
|
||||||
Stream.runForEach(options.ref.changes, Option.match({
|
Stream.runForEach(options.ref.changes, Option.match({
|
||||||
onSome: upstreamValue => Effect.andThen(
|
onSome: upstreamValue => Effect.whenEffect(
|
||||||
Effect.all([Schema.encode(options.schema)(upstreamValue), internalRef]),
|
|
||||||
([encodedUpstreamValue, internalValue]) => Effect.when(
|
|
||||||
Effect.andThen(
|
Effect.andThen(
|
||||||
Ref.set(internalRef, encodedUpstreamValue),
|
Schema.encode(options.schema)(upstreamValue),
|
||||||
Ref.set(disabledRef, false),
|
encodedUpstreamValue => Ref.set(internalRef, encodedUpstreamValue),
|
||||||
),
|
),
|
||||||
() => encodedUpstreamValue !== internalValue,
|
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)))),
|
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, disabled] = yield* useSubscribeRefs(internalRef, disabledRef)
|
|
||||||
const onChange = yield* useCallbackSync((e: React.ChangeEvent<HTMLInputElement>) => Ref.set(
|
|
||||||
internalRef,
|
|
||||||
e.target.value,
|
|
||||||
), [internalRef])
|
|
||||||
|
|
||||||
const setDisabled = yield* useCallbackSync((setStateAction: React.SetStateAction<boolean>) =>
|
const setDisabled = yield* useCallbackSync((setStateAction: React.SetStateAction<boolean>) =>
|
||||||
Effect.andThen(
|
Effect.andThen(
|
||||||
@@ -98,5 +94,9 @@ export const useOptionalInput: {
|
|||||||
),
|
),
|
||||||
[disabledRef, options.ref, internalRef, options.schema])
|
[disabledRef, options.ref, internalRef, options.schema])
|
||||||
|
|
||||||
|
const [disabled] = yield* useSubscribeRefs(disabledRef)
|
||||||
|
const [value, setValue] = yield* useRefState(internalRef)
|
||||||
|
const onChange = React.useCallback((e: React.ChangeEvent<HTMLInputElement>) => setValue(e.target.value), [setValue])
|
||||||
|
|
||||||
return { value, onChange, disabled, setDisabled, error }
|
return { value, onChange, disabled, setDisabled, error }
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ const Input = Component.makeUntraced(function* Input() {
|
|||||||
<Container>
|
<Container>
|
||||||
<IntTextFieldInputFC ref={intRef1} />
|
<IntTextFieldInputFC ref={intRef1} />
|
||||||
<StringTextFieldInputFC ref={stringRef} />
|
<StringTextFieldInputFC ref={stringRef} />
|
||||||
<TextField.Root value={str} onChange={e => setStr(e.target.value)} />
|
<StringTextFieldInputFC ref={stringRef} />
|
||||||
</Container>
|
</Container>
|
||||||
)
|
)
|
||||||
}).pipe(
|
}).pipe(
|
||||||
|
|||||||
Reference in New Issue
Block a user