Interactive Widgets
InteractiveComponent<A, Ctx> is the richer widget contract used by stateful reusable widgets like TextInput, SelectList, ScrollView, and TreeView.
Use it when plain Component<A> stops being expressive enough.
What It Adds
Compared to Component<A>, interactive widgets get:
ComponentInputinstead of rawEventKindHandlerResponse<A>instead of just actionsneeds_renderfor local-state-only updates- a natural integration point for
ComponentHost
The Trait
pub trait InteractiveComponent<A, Ctx = DefaultBindingContext> { type Props<'a> where Self: 'a;
fn update( &mut self, input: ComponentInput<'_, Ctx>, props: Self::Props<'_>, ) -> HandlerResponse<A> { HandlerResponse::ignored() }
fn render(&mut self, frame: &mut Frame, area: Rect, props: Self::Props<'_>);}Routed Input
ComponentInput keeps semantic commands and raw input in the same model:
pub enum ComponentInput<'a, Ctx> { Command { name: &'a str, ctx: Ctx }, Key(KeyEvent), Mouse(MouseEvent), Scroll { .. }, Resize(u16, u16), Tick,}Routing rule:
- if keybindings resolve a command, the widget receives
Command - otherwise it receives the raw
Key
That lets a list react to next or select while a text input still sees literal characters.
Why HandlerResponse Matters
Interactive widgets can say more than “here are some actions”.
They can also say:
- whether they consumed the event
- whether the UI should re-render even if app state did not change
That matters for local widget mechanics like:
- moving a text cursor
- updating a scroll offset
- expanding a tree row
Direct Usage Without a Host
You do not need ComponentHost to use an interactive widget.
use tui_dispatch_components::{ ComponentInput, InteractiveComponent, TextInput, TextInputProps, TextInputRenderProps, TextInputStyle,};use std::rc::Rc;
#[derive(Clone, Debug)]enum Action { QueryChanged(String), Submit,}
let mut input = TextInput::new();
let response = <TextInput as InteractiveComponent<Action>>::update( &mut input, ComponentInput::Key(key_event), TextInputProps { value: &state.query, placeholder: "Type to search", is_focused: true, style: TextInputStyle::default(), on_change: Rc::new(Action::QueryChanged), on_submit: Rc::new(|_| Action::Submit), on_cursor_move: None, on_cancel: None, },);
for action in response.actions { store.dispatch(action);}
if response.needs_render { // schedule or perform a render}
input.render_widget( frame, area, TextInputRenderProps { value: &state.query, placeholder: "Type to search", is_focused: true, style: TextInputStyle::default(), },);This pattern already solves the biggest limitation of plain Component<A>: local widget state can request a render without inventing fake app actions.
Built-In Interactive Widgets
These widgets implement InteractiveComponent<A, Ctx> today:
TextInputSelectListScrollViewTreeView
They also expose render-only helpers like render_widget(...) when you want to separate input handling from rendering.
Composing Interactive Widgets
Interactive widgets are especially useful as building blocks for your own reusable widgets.
Typical pattern:
- your widget owns one or more child widgets
update(...)delegates input to those children- your widget translates child-local actions into your app actions or local state changes
render(...)draws the composed result
That is the pattern used by the log-viewer example’s filter panes and detail viewer.
State Ownership
Interactive widgets still follow the same boundary:
Keep in app state:
- query text
- selected ids
- modes and filters
- loaded data
Keep local to the widget:
- cursor position
- viewport height
- scroll offset
- temporary expansion bookkeeping
Interactive widgets make the local side ergonomic, but they do not replace your store.
When to Use This Layer
Use InteractiveComponent when:
- widgets react to semantic commands and raw keys
- local UI mechanics need
needs_render - you are composing pre-built widgets into a larger reusable widget
Stay with Component<A> when:
- raw
EventKindis enough - the surrounding event wiring is small
- local-state-only renders are not a concern
Move up to Component Host when you want mounted instances and one-time props wiring.