Input work
All checks were successful
Lint / lint (push) Successful in 42s

This commit is contained in:
Julien Valverdé
2025-08-18 03:11:09 +02:00
parent baaa3aa844
commit ea5b5f9ac2
5 changed files with 42 additions and 32 deletions

View File

@@ -3,12 +3,13 @@ export * from "./useCallbackPromise.js"
export * from "./useCallbackSync.js"
export * from "./useContext.js"
export * from "./useEffect.js"
export * from "./useFork.js"
export * from "./useInput.js"
export * from "./useLayoutEffect.js"
export * from "./useMemo.js"
export * from "./useOnce.js"
export * from "./useOptionalInput.js"
export * from "./useRefFromReactiveValue.js"
export * from "./useRefFromState.js"
export * from "./useRefState.js"
export * from "./useScope.js"
export * from "./useStreamFromReactiveValues.js"

View File

@@ -1,9 +1,8 @@
import { type Duration, Effect, flow, identity, Option, type ParseResult, Ref, Schema, Stream, SubscriptionRef } from "effect"
import * as React from "react"
import { useCallbackSync } from "./useCallbackSync.js"
import { useFork } from "./useFork.js"
import { useOnce } from "./useOnce.js"
import { useSubscribeRefs } from "./useSubscribeRefs.js"
import { useRefFromState } from "./useRefFromState.js"
export namespace useInput {
@@ -23,12 +22,14 @@ export namespace useInput {
export const useInput: {
<A, R>(options: useInput.Options<A, R>): Effect.Effect<useInput.Result, ParseResult.ParseError, R>
} = Effect.fnUntraced(function* <A, R>(options: useInput.Options<A, R>) {
const internalRef = yield* useOnce(() => options.ref.pipe(
Effect.andThen(Schema.encode(options.schema)),
Effect.andThen(SubscriptionRef.make),
))
const internalState = React.useState(yield* useOnce(() => Effect.andThen(
options.ref,
Schema.encode(options.schema),
)))
const [value, setValue] = internalState
const [error, setError] = React.useState(Option.none<ParseResult.ParseError>())
const internalRef = yield* useRefFromState(internalState)
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.
@@ -54,11 +55,6 @@ export const useInput: {
),
], { concurrency: "unbounded" }), [options.ref, options.schema, options.debounce, internalRef])
const [value] = yield* useSubscribeRefs(internalRef)
const onChange = yield* useCallbackSync((e: React.ChangeEvent<HTMLInputElement>) => Ref.set(
internalRef,
e.target.value,
), [internalRef])
const onChange = React.useCallback((e: React.ChangeEvent<HTMLInputElement>) => setValue(e.target.value), [])
return { value, onChange, error }
})

View File

@@ -1,12 +0,0 @@
import { Effect, Ref, SubscriptionRef } from "effect"
import { useEffect } from "./useEffect.js"
import { useOnce } from "./useOnce.js"
export const useRefFromReactiveValue: {
<A>(value: A): Effect.Effect<SubscriptionRef.SubscriptionRef<A>>
} = Effect.fnUntraced(function*(value) {
const ref = yield* useOnce(() => SubscriptionRef.make(value))
yield* useEffect(() => Ref.set(ref, value), [value])
return ref
})

View File

@@ -0,0 +1,20 @@
import { Effect, Equivalence, Ref, Stream, SubscriptionRef } from "effect"
import type * as React from "react"
import { useEffect } from "./useEffect.js"
import { useFork } from "./useFork.js"
import { useOnce } from "./useOnce.js"
export const useRefFromState: {
<A>(state: readonly [A, React.Dispatch<React.SetStateAction<A>>]): Effect.Effect<SubscriptionRef.SubscriptionRef<A>>
} = Effect.fnUntraced(function*([value, setValue]) {
const ref = yield* useOnce(() => SubscriptionRef.make(value))
yield* useEffect(() => Ref.set(ref, value), [value])
yield* useFork(() => Stream.runForEach(
Stream.changesWith(ref.changes, Equivalence.strict()),
v => Effect.sync(() => setValue(v)),
), [setValue])
return ref
})