Use Element Refs
Goal: get a handle to a real DOM element — to focus it, measure it, or call an imperative browser API on it.
Declare a SubscriptionRef<Option<HTMLElement>>, attach it with the ref prop, and either react to the element appearing (a scoped observer on .changes) or read it later inside a handler.
import { h } from "@weftui/core";
import { Effect, Option, pipe, Stream, SubscriptionRef } from "effect";
const AutoFocusInput = () =>
Effect.gen(function* () {
const inputRef = yield* SubscriptionRef.make<Option.Option<HTMLInputElement>>(Option.none());
// Observe the element becoming available, once, and focus it.
yield* pipe(
inputRef.changes,
Stream.filter(Option.isSome),
Stream.take(1),
Stream.runForEach((el) => Effect.sync(() => el.value.focus())),
Effect.forkScoped, // ← ties the observer to the component's instance scope
);
return yield* h.input({ ref: inputRef, type: "text", placeholder: "I'm focused!" });
});How it works
- The
refprop takes aSubscriptionRef<Option<T>>. The renderer sets it toOption.some(element)once, when the element is created — so the ref is anOption:Noneuntil mount,Some(el)after. - React to mount by observing
ref.changes:Stream.filter(Option.isSome)waits for the element,Stream.take(1)takes just the first appearance, andStream.runForEachdoes the imperative work. This is the equivalent of a mount effect. - Use
Effect.forkScoped, notEffect.fork.forkScopedties the observer fiber to the component's instance scope (the ambientScopethe renderer provides), so it lives as long as the component is mounted. A bareEffect.forkbinds to the transient component-body fiber and is interrupted the instant the generator returns — the observer would never fire.
Read a ref imperatively
When you only need the element later (e.g. in a click handler), skip the observer and read the ref on demand:
const scroll = () =>
Effect.gen(function* () {
const el = yield* SubscriptionRef.get(targetRef);
if (Option.isSome(el)) el.value.scrollIntoView({ behavior: "smooth" });
});Notes
- A plain
Refsuffices if you only read the element imperatively; useSubscriptionRefwhen you need to react to it becoming available. - Refs are set once at element creation and are not cleared on unmount.
- Coming from React:
SubscriptionRef.make<Option<T>>(Option.none())↔useRef<T>(null); theStream.filter(Option.isSome)observer ↔ auseEffectmount guard.
See also
- Reactive Primitives —
SubscriptionRefand.changes - Author Components — instance scope and
Effect.forkScoped - examples/element-ref — auto-focus, element measurement, and imperative scroll via refs