Skip to content

FAQ

Do I need Tokio?

If you use DispatchRuntime / EffectRuntime, you’ll typically be running inside a Tokio runtime because:

  • the runtimes use tokio::select! internally
  • async patterns (TaskManager, Subscriptions, async effects) obviously require it

If your app is fully synchronous and you don’t want Tokio, you can still use the lower-level pieces (Store, EffectStore, reducers, components) and write your own loop.

Where should async code live?

Not in the reducer.

Recommended:

  • reducer mutates state and returns DispatchResult<Effect>
  • effect handler executes effects (spawn tasks, do IO)
  • async completion sends a normal action back (Did* style)

This keeps reducers easy to test and makes side effects explicit.

How do I re-render when a component updates internal state?

Internal component state (cursor position, scroll offsets) lives in &mut self, so you need a way to request a render even when no app state changes.

Options:

  • Return an app action that causes the reducer to return true / DispatchResult::changed() (common, simple).
  • In your EventBus handler, return HandlerResponse::ignored().with_render().with_consumed(true) when the component handled the event but emitted no actions.

If you’re writing reusable components, consider an explicit “Render” action (see examples/weather which has Action::Render).

For more on routing, see Event Bus.

What’s the difference between Effects and TaskManager?

  • Effect is your app’s enum describing side effects as data.
  • TaskManager is a runtime helper that executes async work and gives you cancellation/debouncing.

Most apps use them together:

  • reducer returns Effect::FetchThing
  • effect handler uses ctx.tasks().spawn("fetch", async { Action::DidLoad(...) })

How do I do periodic ticks / background polling?

Enable subscriptions and use Subscriptions (usually through EffectRuntime):

  • interval("tick", Duration::from_millis(100), || Action::Tick)
  • interval_immediate(...) if you want an initial fire
  • stream(...) to forward a stream into actions

See Async Patterns and examples/weather.

How do I enable the debug overlay?

Use DebugLayer and wire it into the runtime:

  • DebugLayer::simple() uses F12 as the toggle key
  • your state type must implement DebugState for the state overlay

Example:

use tui_dispatch::debug::DebugLayer;
#[derive(Default, tui_dispatch::DebugState)]
struct AppState {
count: i32,
}
let debug: DebugLayer<Action> = DebugLayer::simple().active(true);
let mut runtime = DispatchRuntime::new(AppState::default(), reducer).with_debug(debug);

While enabled:

  • S opens the state tree
  • A opens the action log

How do I test async flows?

You usually don’t test “async” directly.

Test the pieces:

  • reducer emits the right Effect
  • reducer handles Did* actions correctly

EffectStoreTestHarness supports this well:

  • dispatch intent -> drain effects
  • complete Did* action -> process_emitted()

See Tutorial: Fetching Data from an API.


See Also