DioxusLabs/dioxus · error
The hook list is already borrowed: This error is likely caus
Error message
The hook list is already borrowed: This error is likely caused by trying to use hook inside a hook which violates the rules of hooks.
What it means
Each scope's hooks live in a RefCell list that use_hook borrows mutably. This panic fires when try_borrow_mut fails, i.e. a hook is requested while the hook list is already borrowed. In practice this is a re-entrant hook call: another hook is invoked inside the initializer closure passed to use_hook (or a similar hook-creating closure) while the outer use_hook still holds the borrow, violating the rules of hooks.
Source
Thrown at packages/core/src/scope_context.rs:343
/// Queue an effect to run after the next render
pub(crate) fn queue_effect(&self, f: impl FnOnce() + 'static) {
Runtime::with(|rt| rt.queue_effect(self.id, f));
}
/// Store a value in the hook list, returning the value.
pub(crate) fn use_hook<State: Clone + 'static>(
&self,
initializer: impl FnOnce() -> State,
) -> State {
let cur_hook = self.hook_index.get();
// The hook list works by keeping track of the current hook index and pushing the index forward
// while retrieving the hook value.
self.hook_index.set(cur_hook + 1);
let mut hooks = self.hooks
.try_borrow_mut()
.expect("The hook list is already borrowed: This error is likely caused by trying to use hook inside a hook which violates the rules of hooks.");
// Try and retrieve the hook value if it exists
if let Some(existing) = self.use_hook_inner::<State>(&mut hooks, cur_hook) {
return existing;
}
// Otherwise, initialize the hook value. In debug mode, we allow hook types to change after a hot patch
self.push_hook_value(&mut hooks, cur_hook, initializer())
}
// The interior version that gets monomorphized by the `State` type but not the `initializer` type.
// This helps trim down binary sizes
fn use_hook_inner<State: Clone + 'static>(
&self,
hooks: &mut Vec<Box<dyn std::any::Any>>,
cur_hook: usize,
) -> Option<State> {
hooks.get(cur_hook).and_then(|inn| {View on GitHub (pinned to 393d190a80)
Solutions
- Move nested hook calls to the top level of the custom hook function, then pass the results into use_hook
- Never call other hooks inside a use_hook initializer/cleanup; hoist them out
- Write custom hooks as plain functions that call other hooks directly: fn use_thing() { let s = use_signal(..); ... }
- Audit every closure in the failing hook for hidden dioxus::prelude hook calls
Example fix
// before
fn use_counter() -> Signal<i32> {
use_hook(|| {
let count = use_signal(|| 0); // panics: hook inside hook borrow
count
})
}
// after
fn use_counter() -> Signal<i32> {
use_signal(|| 0) // hooks only at the top level of the custom hook
} Defensive patterns
Strategy: validation
Prevention
- Only call hooks at the top level of components or custom hook functions — never inside closures passed to other hooks
- Write custom hooks as plain functions calling other hooks directly, not as use_hook wrappers
- Grep custom hooks for `use_` calls nested inside move || closures during code review
- Keep hook ordering unconditional, mirroring React's rules of hooks
When it happens
Trigger: Writing a custom hook as use_hook(|| { use_signal(|| 0) ... }) so the inner hook call runs inside the outer hook's borrow; calling any dioxus hook (use_signal, use_state, use_resource, use_effect...) inside the initializer or cleanup closure of another hook; custom hooks that invoke hook-creating helpers from within a closure.
Common situations: Refactoring a component into a custom hook and leaving hook calls inside a closure; wrapping hook initialization in helper closures or factories; copy-pasting hook bodies into use_hook initializers.
Related errors
- The tree has not been built yet. Make sure to call rebuild o
- Callback was called after the runtime was dropped
- Must be called in a descendant of a Router component
- use_route must have access to a router
- todo: convert_resize_data in dioxus-native. requires support
AI-assisted analysis of DioxusLabs/dioxus@393d190a80 (2026-08-16).
Data as JSON: /api/errors/c2c63f81f62c7c5e.
Report an issue: GitHub.