leptos-rs/leptos · error

internal error: entered unreachable code

Error message

internal error: entered unreachable code

What it means

`rebuild()` for a reactive property binding (`value.rebuild(&mut state, &key)`) asserts the effect's previous property state is `Some` so the DOM property can be diffed. The `unreachable!()` panic means the effect re-ran with `prev == None` — the seeded `prev_value` was missing (effect created without a value, async value unresolved, or value already taken).

Source

Thrown at tachys/src/reactive_graph/property.rs:66

                value.rebuild(&mut state, &key);
                state
            } else {
                value.build(&el, &key)
            }
        })
    }

    fn rebuild(mut self, state: &mut Self::State, key: &str) {
        let prev_value = state.take_value();
        let key = key.to_owned();
        *state = RenderEffect::new_with_value(
            move |prev| {
                let value = self.invoke();
                if let Some(mut state) = prev {
                    value.rebuild(&mut state, &key);
                    state
                } else {
                    unreachable!()
                }
            },
            prev_value,
        );
    }

    fn into_cloneable(self) -> Self::Cloneable {
        self.into_shared()
    }

    fn into_cloneable_owned(self) -> Self::CloneableOwned {
        self.into_shared()
    }
}

macro_rules! property_reactive {
    ($name:ident, <$($gen:ident),*>, $v:ty, $( $where_clause:tt )*) =>
    {

View on GitHub (pinned to 32d20f6c9d)

Solutions

  1. Update signals only after the element and its attribute states are built/hydrated.
  2. Seed property effects with `RenderEffect::new_with_value` and an initial value in custom attribute implementations.
  3. Avoid resetting the same property state more than once.
  4. Upgrade to the latest leptos/tachys release where effect seeding is guaranteed.

Example fix

// before
*state = RenderEffect::new_with_value(move |prev| { /* expects Some */ }, prev_value);
// after
*state = RenderEffect::new_with_value(
    move |prev: Option<PropState>| { /* Some guaranteed */ },
    prev_value.unwrap_or_else(|| initial_prop_state(&key)),
);
Defensive patterns

Strategy: fallback

Validate before calling

// Gate property updates on mounted state
let mounted = RwSignal::new(false);
create_effect(move |_| {
    if !mounted.get() { return; }
    let _ = prop_signal.get();
});
// mounted.set(true) after the element with prop: bindings mounts

Type guard

fn prop_prev_present(prev: &Option<PropState>) -> bool { prev.is_some() }

Try / catch

// Panic; use hook for diagnostics
std::panic::set_hook(Box::new(|info| {
    if info.to_string().contains("entered unreachable code") {
        leptos::logging::error!("property rebuild without prev: {info}");
    }
}));

Prevention

When it happens

Trigger: A signal driving `prop:{name}={...}` updates while the property effect has no stored previous value: rebuild before build/hydrate committed state, async-seeded effect firing early, or a second rebuild after the value was taken by `reset`.

Common situations: Property bindings updated during SSR hydration before the initial value is stored; dynamic attribute sets (`{..props}` spread) whose state is reset on branch swap; mixing leptos crate versions in a workspace.

Related errors


AI-assisted analysis of leptos-rs/leptos@32d20f6c9d (2026-09-01). Data as JSON: /api/errors/87d4408f5856a774. Report an issue: GitHub.