diff --git a/packages/effect-fc/src/hooks/Hooks/useInput.ts b/packages/effect-fc/src/hooks/Hooks/useInput.ts
index 4780f97..e5444ea 100644
--- a/packages/effect-fc/src/hooks/Hooks/useInput.ts
+++ b/packages/effect-fc/src/hooks/Hooks/useInput.ts
@@ -2,7 +2,7 @@ import { type Duration, Effect, Equal, flow, identity, Option, type ParseResult,
import * as React from "react"
import { useFork } from "./useFork.js"
import { useOnce } from "./useOnce.js"
-import { useRefFromState } from "./useRefFromState.js"
+import { useRefState } from "./useRefState.js"
export namespace useInput {
@@ -22,14 +22,12 @@ export namespace useInput {
export const useInput: {
(options: useInput.Options): Effect.Effect
} = Effect.fnUntraced(function* (options: useInput.Options) {
- const internalState = React.useState(yield* useOnce(() => Effect.andThen(
- options.ref,
- Schema.encode(options.schema),
- )))
- const [value, setValue] = internalState
+ const internalRef = yield* useOnce(() => options.ref.pipe(
+ Effect.andThen(Schema.encode(options.schema)),
+ Effect.andThen(SubscriptionRef.make),
+ ))
const [error, setError] = React.useState(Option.none())
- 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.
@@ -58,6 +56,8 @@ export const useInput: {
),
], { concurrency: "unbounded" }), [options.ref, options.schema, options.debounce, internalRef])
- const onChange = React.useCallback((e: React.ChangeEvent) => setValue(e.target.value), [])
+ const [value, setValue] = yield* useRefState(internalRef)
+ const onChange = React.useCallback((e: React.ChangeEvent) => setValue(e.target.value), [setValue])
+
return { value, onChange, error }
})
diff --git a/packages/effect-fc/src/hooks/Hooks/useRefState.ts b/packages/effect-fc/src/hooks/Hooks/useRefState.ts
index ad9efb0..6cc3980 100644
--- a/packages/effect-fc/src/hooks/Hooks/useRefState.ts
+++ b/packages/effect-fc/src/hooks/Hooks/useRefState.ts
@@ -19,8 +19,9 @@ export const useRefState: {
), [ref])
const setValue = yield* useCallbackSync((setStateAction: React.SetStateAction) =>
- Ref.update(ref, prevState =>
- SetStateAction.value(setStateAction, prevState)
+ Effect.andThen(
+ Ref.updateAndGet(ref, prevState => SetStateAction.value(setStateAction, prevState)),
+ v => setReactStateValue(v),
),
[ref])
diff --git a/packages/example/src/routes/dev/input.tsx b/packages/example/src/routes/dev/input.tsx
index 2e18881..7946fe0 100644
--- a/packages/example/src/routes/dev/input.tsx
+++ b/packages/example/src/routes/dev/input.tsx
@@ -4,7 +4,7 @@ import { Container, TextField } from "@radix-ui/themes"
import { createFileRoute } from "@tanstack/react-router"
import { Schema, SubscriptionRef } from "effect"
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())
@@ -23,11 +23,13 @@ const Input = Component.makeUntraced(function* Input() {
const input2 = yield* useInput({ schema: IntFromString, ref: intRef2 })
+ const [str, setStr] = yield* useRefState(stringRef)
+
return (
-
+ setStr(e.target.value)} />
)
}).pipe(