leptos-rs/leptos · error

internal error: entered unreachable code

Error message

internal error: entered unreachable code

What it means

During `hydrate_async` of a reactive view fragment, tachys builds a `RenderEffect::new_with_async_value` whose closure asserts `prev` is `Some` (the previously rendered sub-view state used by `value.rebuild`). The panic means the effect closure ran while the async initial value had not resolved (slot still `None`) or after the value was taken — the reactive fragment re-rendered before its seed state existed.

Source

Thrown at tachys/src/reactive_graph/mod.rs:276

            {
                let mut fun = fun.clone();
                move |prev| {
                    /// codegen optimisation:
                    fn get_guard(
                        hook: &Option<Arc<dyn throw_error::ErrorHook>>,
                    ) -> Option<throw_error::ResetErrorHookOnDrop>
                    {
                        hook.as_ref()
                            .map(|h| throw_error::set_error_hook(Arc::clone(h)))
                    }
                    let _guard = get_guard(&hook);

                    let value = fun.invoke();
                    if let Some(mut state) = prev {
                        value.rebuild(&mut state);
                        state
                    } else {
                        unreachable!()
                    }
                }
            },
            async move { fun.invoke().hydrate_async(&cursor, &position).await },
        )
        .await
        .into()
    }

    fn into_owned(self) -> Self::Owned {
        self
    }
}

impl<F, V> AddAnyAttr for F
where
    F: ReactiveFunction<Output = V>,
    V: RenderHtml + 'static,

View on GitHub (pinned to 32d20f6c9d)

Solutions

  1. Delay signal updates until after async hydration completes (e.g. run updates in an effect spawned after `hydrate` returns, or gate the signal source on a `hydrated` flag).
  2. Avoid rapid branch swaps (`Show`/`Either`) around async-hydrated reactive fragments during initial load.
  3. Upgrade leptos/tachys — later releases addressed `new_with_async_value` prev-value races.
  4. Reproduce minimally and report to the Leptos tracker if stock components trigger it.

Example fix

// before: signal updated during hydration
create_effect(move |_| { data.update(...); }); // may run pre-hydration
// after: gate until hydrated
let hydrated = RwSignal::new(false);
create_effect(move |_| { if hydrated.get() { data.update(...); } });
// set hydrated = true after view hydrates/mounts
Defensive patterns

Strategy: fallback

Validate before calling

// Block reactive fragment updates until async hydration completes
let hydrated = RwSignal::new(false);
// after hydrate_async resolves in your bootstrap:
hydrated.set(true);
create_effect(move |_| {
    if !hydrated.get() { return; }
    let _ = reactive_fragment_signal.get();
});

Type guard

fn fragment_state_ready(prev: &Option<FragmentState>) -> bool { prev.is_some() }

Try / catch

// Wrap hydration bootstrap with catch_unwind + fallback
let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
    futures::executor::block_on(view.hydrate_async(...))
}));
if result.is_err() { render_client_fallback(); }

Prevention

When it happens

Trigger: A reactive function inside a `<Suspense>`/async-hydrated fragment updates (signal fires) before `hydrate_async`'s future resolves and stores the initial view state; or the fragment is rebuilt/reset while the async value is pending.

Common situations: SSR + async hydration where a global signal changes during initial load (e.g. router navigation, websocket push) before hydration completes; Suspense fallback swapping in the resolved branch and immediately re-running the effect.

Related errors


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