leptos-rs/leptos · error

internal error: entered unreachable code

Error message

internal error: entered unreachable code

What it means

`rebuild()` for a reactive style binding (`value.rebuild(&mut state)`) asserts the effect's previous style state is `Some` so changed styles can be diffed against the DOM. The `unreachable!()` panic means the effect ran with `prev == None`: the seed value was absent at creation (non-seeding effect, unresolved async value, or value already taken).

Source

Thrown at tachys/src/reactive_graph/style.rs:171

            if let Some(mut state) = prev {
                value.rebuild(&mut state);
                state
            } else {
                value.build(&el)
            }
        })
    }

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

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

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

    fn dry_resolve(&mut self) {
        self.invoke();
    }

View on GitHub (pinned to 32d20f6c9d)

Solutions

  1. Defer style-driving signal updates until after the view is fully built/hydrated.
  2. Initialize style effects with `RenderEffect::new_with_value(f, initial_style_state)` in custom code.
  3. Ensure `reset`/`rebuild` ordering: never rebuild a state whose value was taken without reseeding.
  4. Update leptos/tachys to a release with the effect-seeding fix.

Example fix

// before
RenderEffect::new_with_value(move |prev| { /* expects Some */ }, prev_value /* may be None */);
// after
RenderEffect::new_with_value(
    move |prev: Option<StyleState>| { /* Some guaranteed */ },
    prev_value.unwrap_or_else(StyleState::initial),
);
Defensive patterns

Strategy: fallback

Validate before calling

// Defer style updates until mounted
let mounted = RwSignal::new(false);
create_effect(move |_| {
    if !mounted.get() { return; }
    let _ = style_signal.get();
});
// mounted.set(true) in on_mount

Type guard

fn style_prev_present(prev: &Option<StyleState>) -> bool { prev.is_some() }

Try / catch

// Panic; diagnostics via hook
std::panic::set_hook(Box::new(|info| {
    if info.to_string().contains("unreachable") {
        leptos::logging::error!("style rebuild without prev state: {info}");
    }
}));

Prevention

When it happens

Trigger: A signal driving `style:` bindings updates while the style effect's value slot is empty — effect re-run before `build`/`hydrate` stored `prev_value`, an async-seeded effect firing early, or a rebuild after `reset` took the value.

Common situations: Style bindings reacting to data fetched right after hydration; view branch swaps (`Show`/`Either`) resetting style state and then updating the same signal; SSR-hydrated pages where a reactive style fires during hydration.

Related errors


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