From 4a6ae5dfd8221668b80bbaf4574c5745b408ba1d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Valverd=C3=A9?= Date: Tue, 19 Aug 2025 02:19:01 +0200 Subject: [PATCH] Fix --- packages/effect-fc/src/hooks/Hooks/useInput.ts | 16 ++++++++-------- .../effect-fc/src/hooks/Hooks/useRefState.ts | 5 +++-- packages/example/src/routes/dev/input.tsx | 6 ++++-- 3 files changed, 15 insertions(+), 12 deletions(-) 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(