Hooks
Hooks are the connection point between function components and the runtime. A component function runs again every frame; hooks let state, async tasks, event handlers, and context reads attach to the persistent component instance rather than to a temporary Element declaration.
If you have read the counter tutorial, treat this page as a reference: first find the hook you need, then return for ordering, dependency, or custom-hook rules.
Which hook to choose
Section titled “Which hook to choose”| Need | Hook | Example |
|---|---|---|
| Component-private reactive state | use_state | Counter tutorial |
| Start one async task on mount | use_future | Timer, background polling |
| Run a synchronous side effect when dependencies change | use_effect | Clamp a cursor based on derived data |
| Run an async side effect when dependencies change | use_async_effect | Async validation, request refresh |
Maintain data / loading / error | use_async_state | Async data states |
| Cache a pure computed value | use_memo | Custom hook |
| Read a Provider-injected value | use_context / try_use_context | Custom provider |
| Register keyboard/mouse events | use_event_handler | Input isolation |
| Register events with hit testing | use_event_handler_with_options | Local mouse wheel, clicks inside component area |
| Declare a modal input layer | use_input_layer | Modal surface |
| Read terminal or component size | use_terminal_size / use_previous_size | Responsive layout, previous-frame area measurement |
| Request application exit | use_exit | All exit-capable examples |
| Clean up when a component unmounts | use_on_drop | Unsubscribe external resources |
| Insert content before the terminal render area | use_insert_before | Advanced escape hatch for small terminal prefixes |
Feature-gated capabilities include use_atom (atom feature) and use_router / use_navigate (router feature). They are covered in State and Routing.
Call order must be stable
Section titled “Call order must be stable”Hooks are indexed by call order. On the first frame, when use_state, use_memo, or use_effect is called, the runtime places the corresponding hook into the component instance’s hook list. On the next frame, it retrieves old hooks in the same order.
Therefore hook calls must not be placed inside if, for, or match branches, and must not come after a path that may return early:
If the first frame has enabled=true and the second has enabled=false, the index for cursor shifts and the runtime may read a hook of the wrong type.
The correct pattern is to keep hook order fixed and put conditions in hook arguments, event handlers, or the render tree:
Control flow inside element! does not affect hook order. See Control flow syntax for complete examples.
use_state
Section titled “use_state”use_state creates component-local reactive state. The returned State<T> is a lightweight handle that can be passed by value into closures, child component props, or hand-written component props.
Read state with get() or read(); write with set(), write(), or operators such as +=. Writes wake the render loop so the next frame reruns the component function.
State<T> requires T: Unpin + Send + Sync + 'static, which lets background tasks hold a handle and write state. During draw, if you are only passing state to a Ratatui stateful widget, use write_no_update() so drawing itself does not trigger another update. Native widget bridge shows this boundary.
use_future
Section titled “use_future”use_future starts one future when the component mounts. It fits “run this task while the component is alive,” such as a timer loop:
The use_future initializer is registered only on the first frame. Do not use it for “rerun a request when dependencies change”; use use_async_effect or use_async_state for that.
use_effect and use_memo
Section titled “use_effect and use_memo”use_effect means “do something when dependencies change.” Dependencies are compared with PartialEq and do not require Clone:
use_memo means “reuse this value while dependencies stay equal.” It fits pure computations such as filtering a command list by query:
Do not use use_memo for hidden side effects, and do not use use_effect just to cache a side-effect result. The distinction is simple: memo returns a value; effect does work.
use_async_effect and use_async_state
Section titled “use_async_effect and use_async_state”use_async_effect replaces the old future and starts a new one when dependencies change:
For the common request triad, use use_async_state. It combines use_state + use_async_effect: when dependencies change it sets loading=true, writes data on success, writes error on failure, and sets loading=false at the end.
Old data is retained during refresh. Terminal UIs usually benefit from keeping old content visible while showing loading state, rather than clearing the screen on every refresh.
use_context
Section titled “use_context”use_context::<T>() reads the nearest value injected by ContextProvider; try_use_context::<T>() returns None when no provider exists or when the context is currently borrowed.
Keep context borrows inside small blocks and copy out only the fields needed for rendering. That prevents accidentally holding an old borrow while calling more hooks, entering nested providers, or requesting mutable context.
Providers fit subtree-level capabilities such as theme, current workspace, or local service objects. Use Atom only for cross-page, process-wide state.
use_event_handler and use_input_layer
Section titled “use_event_handler and use_input_layer”use_event_handler registers keyboard/mouse events on the current input layer. A handler must return EventResult:
Consumed cuts off later handlers; Ignored means this handler did not handle the event, so same-layer or lower-layer handlers may continue.
For events that should work only inside a component’s area, use use_event_handler_with_options:
hit_test filters only mouse events. Keyboard events have no coordinates and are still delivered normally. The hit area comes from the component’s previous-frame draw area, so do not rely on it for precise mouse interaction before the first draw.
Modal interactions declare an exclusive layer with use_input_layer(open, blocks_lower):
InputLayer handles are valid only within the same frame and are reminted every frame. Do not store them in use_state. If you pass the layer to Modal(layer: Some(layer)), Current handlers inside the Modal subtree automatically belong to that layer, and background components stop seeing the same keys.
In a hand-written Component, calling use_event_handler or use_input_layer requires upgrading hooks with a context stack first:
Function components get this from #[component] automatically.
Size, exit, and cleanup
Section titled “Size, exit, and cleanup”use_terminal_size() returns the current terminal size and updates after Resize events. It is one of the few hooks that can be called directly inside a hand-written Component without manually calling with_context_stack.
use_previous_size() returns the component’s previous-frame draw area. It is useful for component logic that needs to know its own region. It is not the final layout result for the current frame; it is the recorded previous-frame area.
use_exit() returns a 'static exit closure. Calling it requests application exit on the next update:
use_on_drop runs a callback when the component is destroyed, which is useful for unsubscribing external resources. Do not use State handles to write UI state inside that callback; the component is already on its unmount path.
Low-level escape hatch: use_insert_before
Section titled “Low-level escape hatch: use_insert_before”use_insert_before() returns an InsertBeforeHandler that can insert extra content before the current terminal render area. It is not part of the layout system and does not create component tree nodes; it is for small terminal-level additions such as IME candidate bars or debug prefixes.
insert_before(height, callback) gives direct access to the lower-level buffer callback. render_before(widget, height) renders a Ratatui Widget into the prefix area. Call finish() afterward to wake the render loop; the queue is handed to the terminal after the next update.
Do not use it for ordinary UI, modals, lists, or page content. Those belong in the component tree with View, Border, Modal, ScrollView, or a custom Component.
Custom hooks
Section titled “Custom hooks”Application-level custom hooks should usually be ordinary functions that compose built-in hooks in a stable order:
This function still obeys hook ordering rules. The benefit is a small API surface: the component receives a snapshot for rendering, while state, derived data, and event handling are captured in a well-named use_* function. See Custom hook for the full example.
Implement the lower-level Hook trait only when the capability needs its own lifecycle callbacks, for example:
- It needs to poll an external future or manual waker in
poll_change. - It needs runtime context in
pre_component_update/post_component_update. - It needs to record area or draw-time information in
pre_component_draw/post_component_draw. - It needs to release external resources in
on_drop.
The standard low-level custom-hook shape is Sealed trait + UseXxxImpl + Hooks::use_hook. Start with compositional hooks for ordinary business logic; drop to Hook only when existing hooks cannot express the lifecycle.