Weftv0.23.1
GitHub

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.

typescript
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 ref prop takes a SubscriptionRef<Option<T>>. The renderer sets it to Option.some(element) once, when the element is created — so the ref is an Option: None until 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, and Stream.runForEach does the imperative work. This is the equivalent of a mount effect.
  • Use Effect.forkScoped, not Effect.fork. forkScoped ties the observer fiber to the component's instance scope (the ambient Scope the renderer provides), so it lives as long as the component is mounted. A bare Effect.fork binds 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:

typescript
const scroll = () =>
  Effect.gen(function* () {
    const el = yield* SubscriptionRef.get(targetRef);
    if (Option.isSome(el)) el.value.scrollIntoView({ behavior: "smooth" });
  });

Notes

  • A plain Ref suffices if you only read the element imperatively; use SubscriptionRef when 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); the Stream.filter(Option.isSome) observer ↔ a useEffect mount guard.

See also