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

  1. Move nested hook calls to the top level of the custom hook function, then pass the results into use_hook
  2. Never call other hooks inside a use_hook initializer/cleanup; hoist them out
  3. Write custom hooks as plain functions that call other hooks directly: fn use_thing() { let s = use_signal(..); ... }
  4. 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

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


AI-assisted analysis of DioxusLabs/dioxus@393d190a80 (2026-08-16). Data as JSON: /api/errors/c2c63f81f62c7c5e. Report an issue: GitHub.