Fix
All checks were successful
Lint / lint (push) Successful in 39s

This commit is contained in:
Julien Valverdé
2025-08-19 02:19:01 +02:00
parent e665a9d691
commit 4a6ae5dfd8
3 changed files with 15 additions and 12 deletions

View File

@@ -2,7 +2,7 @@ import { type Duration, Effect, Equal, flow, identity, Option, type ParseResult,
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"
import { useRefFromState } from "./useRefFromState.js" import { useRefState } from "./useRefState.js"
export namespace useInput { export namespace useInput {
@@ -22,14 +22,12 @@ export namespace useInput {
export const useInput: { export const useInput: {
<A, R>(options: useInput.Options<A, R>): Effect.Effect<useInput.Result, ParseResult.ParseError, R> <A, R>(options: useInput.Options<A, R>): Effect.Effect<useInput.Result, ParseResult.ParseError, R>
} = Effect.fnUntraced(function* <A, R>(options: useInput.Options<A, R>) { } = Effect.fnUntraced(function* <A, R>(options: useInput.Options<A, R>) {
const internalState = React.useState(yield* useOnce(() => Effect.andThen( const internalRef = yield* useOnce(() => options.ref.pipe(
options.ref, Effect.andThen(Schema.encode(options.schema)),
Schema.encode(options.schema), Effect.andThen(SubscriptionRef.make),
))) ))
const [value, setValue] = internalState
const [error, setError] = React.useState(Option.none<ParseResult.ParseError>()) const [error, setError] = React.useState(Option.none<ParseResult.ParseError>())
const internalRef = yield* useRefFromState(internalState)
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 encoded value is actually different. This avoids infinite re-render loops.
@@ -58,6 +56,8 @@ export const useInput: {
), ),
], { concurrency: "unbounded" }), [options.ref, options.schema, options.debounce, internalRef]) ], { concurrency: "unbounded" }), [options.ref, options.schema, options.debounce, internalRef])
const onChange = React.useCallback((e: React.ChangeEvent<HTMLInputElement>) => setValue(e.target.value), []) const [value, setValue] = yield* useRefState(internalRef)
const onChange = React.useCallback((e: React.ChangeEvent<HTMLInputElement>) => setValue(e.target.value), [setValue])
return { value, onChange, error } return { value, onChange, error }
}) })

View File

@@ -19,8 +19,9 @@ export const useRefState: {
), [ref]) ), [ref])
const setValue = yield* useCallbackSync((setStateAction: React.SetStateAction<A>) => const setValue = yield* useCallbackSync((setStateAction: React.SetStateAction<A>) =>
Ref.update(ref, prevState => Effect.andThen(
SetStateAction.value(setStateAction, prevState) Ref.updateAndGet(ref, prevState => SetStateAction.value(setStateAction, prevState)),
v => setReactStateValue(v),
), ),
[ref]) [ref])

View File

@@ -4,7 +4,7 @@ import { Container, TextField } from "@radix-ui/themes"
import { createFileRoute } from "@tanstack/react-router" import { createFileRoute } from "@tanstack/react-router"
import { Schema, SubscriptionRef } from "effect" import { Schema, SubscriptionRef } from "effect"
import { Component, Memo } from "effect-fc" import { Component, Memo } from "effect-fc"
import { useInput, useOnce } from "effect-fc/hooks" import { useInput, useOnce, useRefState } from "effect-fc/hooks"
const IntFromString = Schema.NumberFromString.pipe(Schema.int()) const IntFromString = Schema.NumberFromString.pipe(Schema.int())
@@ -23,11 +23,13 @@ const Input = Component.makeUntraced(function* Input() {
const input2 = yield* useInput({ schema: IntFromString, ref: intRef2 }) const input2 = yield* useInput({ schema: IntFromString, ref: intRef2 })
const [str, setStr] = yield* useRefState(stringRef)
return ( return (
<Container> <Container>
<IntTextFieldInputFC ref={intRef1} /> <IntTextFieldInputFC ref={intRef1} />
<TextField.Root value={input2.value} onChange={input2.onChange} />
<StringTextFieldInputFC ref={stringRef} /> <StringTextFieldInputFC ref={stringRef} />
<TextField.Root value={str} onChange={e => setStr(e.target.value)} />
</Container> </Container>
) )
}).pipe( }).pipe(