This commit is contained in:
@@ -7,6 +7,7 @@ export * from "./useInput.js"
|
|||||||
export * from "./useLayoutEffect.js"
|
export * from "./useLayoutEffect.js"
|
||||||
export * from "./useMemo.js"
|
export * from "./useMemo.js"
|
||||||
export * from "./useOnce.js"
|
export * from "./useOnce.js"
|
||||||
|
export * from "./useOptionalInput.js"
|
||||||
export * from "./useRefFromReactiveValue.js"
|
export * from "./useRefFromReactiveValue.js"
|
||||||
export * from "./useRefState.js"
|
export * from "./useRefState.js"
|
||||||
export * from "./useScope.js"
|
export * from "./useScope.js"
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { type Duration, Effect, flow, identity, Option, type ParseResult, Ref, Schema, Stream, SubscriptionRef } from "effect"
|
import { type Duration, Effect, 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 { 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"
|
||||||
@@ -17,6 +18,8 @@ export namespace useOptionalInput {
|
|||||||
export interface Result {
|
export interface Result {
|
||||||
readonly value: string
|
readonly value: string
|
||||||
readonly onChange: React.ChangeEventHandler<HTMLInputElement | HTMLTextAreaElement>
|
readonly onChange: React.ChangeEventHandler<HTMLInputElement | HTMLTextAreaElement>
|
||||||
|
readonly disabled: boolean
|
||||||
|
readonly setDisabled: React.Dispatch<React.SetStateAction<boolean>>
|
||||||
readonly error: Option.Option<ParseResult.ParseError>
|
readonly error: Option.Option<ParseResult.ParseError>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -24,7 +27,7 @@ export namespace useOptionalInput {
|
|||||||
export const useOptionalInput: {
|
export const useOptionalInput: {
|
||||||
<A, R>(options: useOptionalInput.Options<A, R>): Effect.Effect<useOptionalInput.Result, ParseResult.ParseError, R>
|
<A, R>(options: useOptionalInput.Options<A, R>): Effect.Effect<useOptionalInput.Result, ParseResult.ParseError, R>
|
||||||
} = Effect.fnUntraced(function* <A, R>(options: useOptionalInput.Options<A, R>) {
|
} = Effect.fnUntraced(function* <A, R>(options: useOptionalInput.Options<A, R>) {
|
||||||
const [internalRef, initialDisabled] = yield* useOnce(() => Effect.andThen(options.ref, upstreamValue =>
|
const [internalRef, disabledRef] = yield* useOnce(() => Effect.andThen(options.ref, upstreamValue =>
|
||||||
Effect.all([
|
Effect.all([
|
||||||
Effect.andThen(
|
Effect.andThen(
|
||||||
Option.match(upstreamValue, {
|
Option.match(upstreamValue, {
|
||||||
@@ -36,11 +39,10 @@ export const useOptionalInput: {
|
|||||||
SubscriptionRef.make,
|
SubscriptionRef.make,
|
||||||
),
|
),
|
||||||
|
|
||||||
Effect.succeed(Option.isNone(upstreamValue)),
|
SubscriptionRef.make(Option.isNone(upstreamValue)),
|
||||||
])
|
])
|
||||||
))
|
))
|
||||||
|
|
||||||
const [disabled, setDisabled] = React.useState(initialDisabled)
|
|
||||||
const [error, setError] = React.useState(Option.none<ParseResult.ParseError>())
|
const [error, setError] = React.useState(Option.none<ParseResult.ParseError>())
|
||||||
|
|
||||||
yield* useFork(() => Effect.all([
|
yield* useFork(() => Effect.all([
|
||||||
@@ -50,12 +52,15 @@ export const useOptionalInput: {
|
|||||||
onSome: upstreamValue => Effect.andThen(
|
onSome: upstreamValue => Effect.andThen(
|
||||||
Effect.all([Schema.encode(options.schema)(upstreamValue), internalRef]),
|
Effect.all([Schema.encode(options.schema)(upstreamValue), internalRef]),
|
||||||
([encodedUpstreamValue, internalValue]) => Effect.when(
|
([encodedUpstreamValue, internalValue]) => Effect.when(
|
||||||
Ref.set(internalRef, encodedUpstreamValue),
|
Effect.andThen(
|
||||||
|
Ref.set(internalRef, encodedUpstreamValue),
|
||||||
|
Ref.set(disabledRef, false),
|
||||||
|
),
|
||||||
() => encodedUpstreamValue !== internalValue,
|
() => encodedUpstreamValue !== internalValue,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
||||||
onNone: () => Effect.sync(() => setDisabled(true)),
|
onNone: () => Ref.set(disabledRef, true),
|
||||||
})),
|
})),
|
||||||
|
|
||||||
// Sync all changes to the internal state with upstream
|
// Sync all changes to the internal state with upstream
|
||||||
@@ -63,18 +68,33 @@ export const useOptionalInput: {
|
|||||||
internalRef.changes.pipe(options.debounce ? Stream.debounce(options.debounce) : identity),
|
internalRef.changes.pipe(options.debounce ? Stream.debounce(options.debounce) : identity),
|
||||||
flow(
|
flow(
|
||||||
Schema.decode(options.schema),
|
Schema.decode(options.schema),
|
||||||
Effect.andThen(v => Ref.set(options.ref, v)),
|
Effect.andThen(v => Ref.set(options.ref, Option.some(v))),
|
||||||
Effect.andThen(() => setError(Option.none())),
|
Effect.andThen(() => setError(Option.none())),
|
||||||
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, internalRef])
|
||||||
|
|
||||||
const [value] = yield* useSubscribeRefs(internalRef)
|
const [value, disabled] = yield* useSubscribeRefs(internalRef, disabledRef)
|
||||||
const onChange = yield* useCallbackSync((e: React.ChangeEvent<HTMLInputElement>) => Ref.set(
|
const onChange = yield* useCallbackSync((e: React.ChangeEvent<HTMLInputElement>) => Ref.set(
|
||||||
internalRef,
|
internalRef,
|
||||||
e.target.value,
|
e.target.value,
|
||||||
), [internalRef])
|
), [internalRef])
|
||||||
|
|
||||||
return { value, onChange, error }
|
const setDisabled = yield* useCallbackSync((setStateAction: React.SetStateAction<boolean>) =>
|
||||||
|
Effect.andThen(
|
||||||
|
Ref.updateAndGet(disabledRef, prevState =>
|
||||||
|
SetStateAction.value(setStateAction, prevState)
|
||||||
|
),
|
||||||
|
|
||||||
|
disabled => !disabled
|
||||||
|
? Ref.set(options.ref, Option.none())
|
||||||
|
: internalRef.pipe(
|
||||||
|
Effect.andThen(Schema.decode(options.schema)),
|
||||||
|
Effect.andThen(v => Ref.set(options.ref, Option.some(v))),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
[disabledRef, options.ref, internalRef, options.schema])
|
||||||
|
|
||||||
|
return { value, onChange, disabled, setDisabled, error }
|
||||||
})
|
})
|
||||||
|
|||||||
Reference in New Issue
Block a user