Skip to content

Effects and Async in Components #8

@BinaryMuse

Description

@BinaryMuse

Problem

The current hook system has use_interval, use_mount, and use_unmount — all synchronous callbacks. There's no use_effect (run a side effect when dependencies change) and no way to do async work inside a component.

In atuin, async work (streaming responses, API calls) lives entirely outside the component tree — spawned as tokio tasks that communicate back via Handle::update(). This works, but it means the component can't own its own async lifecycle. If a component is unmounted while its async work is in flight, there's no automatic cancellation.

What iocraft does

  • use_future() — run an async closure tied to the component's lifetime. Cancelled on unmount.
  • use_effect() — run a side effect when hash-based dependencies change. Synchronous, but combined with use_future covers most patterns.

Questions to explore

use_effect(deps, callback):

  • When deps change, run callback. Classic React pattern.
  • Dependency tracking: hash-based (like iocraft)? Explicit dep list? Or rely on the fact that lifecycle() is already re-called when props/state change, so effects declared conditionally already "react" to changes?
  • Cleanup: should use_effect return a cleanup function that runs before the next effect or on unmount?

Async in components:

  • A use_async or use_future hook that spawns a tokio task tied to the component's lifetime. On unmount, the task is cancelled (AbortHandle).
  • The task would need a way to update the component's state — either a channel back to the framework, or a State<T> handle (like iocraft's copy-based state).
  • This is deeply tied to the runtime model. Today, state mutation happens through Tracked<S> with framework-driven dirty checking. Async tasks would need a way to trigger re-renders from outside the normal lifecycle.

Relationship to Handle:

  • Handle::update() already solves "async work updates state" at the application level. The question is whether per-component async is worth the complexity, or if the Handle pattern is good enough.
  • Per-component async is most valuable when components are reusable and self-contained (e.g., a StreamingMarkdown component that owns its own streaming connection). If components are always app-specific, Handle is fine.

No concrete proposal yet

This needs more real-world pain to drive the design. Capturing it here so we don't lose the thread.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions