0.1.3 #4
@@ -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 }
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -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])
|
||||||
|
|
||||||
|
|||||||
@@ -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(
|
||||||
|
|||||||
Reference in New Issue
Block a user