diff --git a/packages/effect-fc/src/hooks/Hooks/index.ts b/packages/effect-fc/src/hooks/Hooks/index.ts
index 6b67a59..a788fb9 100644
--- a/packages/effect-fc/src/hooks/Hooks/index.ts
+++ b/packages/effect-fc/src/hooks/Hooks/index.ts
@@ -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"
diff --git a/packages/effect-fc/src/hooks/Hooks/useInput.ts b/packages/effect-fc/src/hooks/Hooks/useInput.ts
index d63c8d8..eba241f 100644
--- a/packages/effect-fc/src/hooks/Hooks/useInput.ts
+++ b/packages/effect-fc/src/hooks/Hooks/useInput.ts
@@ -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: {
(options: useInput.Options): Effect.Effect
} = Effect.fnUntraced(function* (options: useInput.Options) {
- 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())
+ 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) => Ref.set(
- internalRef,
- e.target.value,
- ), [internalRef])
-
+ const onChange = React.useCallback((e: React.ChangeEvent) => setValue(e.target.value), [])
return { value, onChange, error }
})
diff --git a/packages/effect-fc/src/hooks/Hooks/useRefFromReactiveValue.ts b/packages/effect-fc/src/hooks/Hooks/useRefFromReactiveValue.ts
deleted file mode 100644
index d3e0f39..0000000
--- a/packages/effect-fc/src/hooks/Hooks/useRefFromReactiveValue.ts
+++ /dev/null
@@ -1,12 +0,0 @@
-import { Effect, Ref, SubscriptionRef } from "effect"
-import { useEffect } from "./useEffect.js"
-import { useOnce } from "./useOnce.js"
-
-
-export const useRefFromReactiveValue: {
- (value: A): Effect.Effect>
-} = Effect.fnUntraced(function*(value) {
- const ref = yield* useOnce(() => SubscriptionRef.make(value))
- yield* useEffect(() => Ref.set(ref, value), [value])
- return ref
-})
diff --git a/packages/effect-fc/src/hooks/Hooks/useRefFromState.ts b/packages/effect-fc/src/hooks/Hooks/useRefFromState.ts
new file mode 100644
index 0000000..a6138f8
--- /dev/null
+++ b/packages/effect-fc/src/hooks/Hooks/useRefFromState.ts
@@ -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: {
+ (state: readonly [A, React.Dispatch>]): Effect.Effect>
+} = 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
+})
diff --git a/packages/example/src/routes/dev/input.tsx b/packages/example/src/routes/dev/input.tsx
index c17b910..6895fdc 100644
--- a/packages/example/src/routes/dev/input.tsx
+++ b/packages/example/src/routes/dev/input.tsx
@@ -1,27 +1,32 @@
import { TextFieldInput } from "@/lib/TextFieldInput"
import { runtime } from "@/runtime"
-import { Container } from "@radix-ui/themes"
+import { Container, TextField } from "@radix-ui/themes"
import { createFileRoute } from "@tanstack/react-router"
import { Console, Schema, Stream, SubscriptionRef } from "effect"
import { Component, Memo } from "effect-fc"
-import { useOnce } from "effect-fc/hooks"
-import { useFork } from "effect-fc/hooks/Hooks/useFork"
+import { useFork, useInput, useOnce, useRefState } from "effect-fc/hooks"
-const IntTextFieldInput = TextFieldInput({ schema: Schema.NumberFromString.pipe(Schema.int()) })
+const IntFromString = Schema.NumberFromString.pipe(Schema.int())
+
+const IntTextFieldInput = TextFieldInput({ schema: IntFromString })
const StringTextFieldInput = TextFieldInput({ schema: Schema.String })
const Input = Component.makeUntraced(function* Input() {
const IntTextFieldInputFC = yield* IntTextFieldInput
const StringTextFieldInputFC = yield* StringTextFieldInput
- const intRef = yield* useOnce(() => SubscriptionRef.make(0))
+ const intRef1 = yield* useOnce(() => SubscriptionRef.make(0))
+ const intRef2 = yield* useOnce(() => SubscriptionRef.make(0))
const stringRef = yield* useOnce(() => SubscriptionRef.make(""))
- yield* useFork(() => Stream.runForEach(intRef.changes, Console.log), [intRef])
+ // yield* useFork(() => Stream.runForEach(intRef1.changes, Console.log), [intRef1])
+
+ const input2 = yield* useInput({ schema: IntFromString, ref: intRef2 })
return (
-
+
+
)