leptos_hydrated 0.5.0

A component to hydrate and manage interactive hydration state in Leptos 0.8
Documentation

Leptos Hydrated

A lightweight library for flicker-free interactive state hydration in Leptos 0.8 that works with or without JavaScript.

Features

  • Flicker-Free: Initializes signals with server-provided state immediately during hydration.
  • Isomorphic: Works naturally in both SSR and CSR contexts.
  • Trait-Based: Use the Hydratable trait to define state and refresh logic in one place.
  • Global & Scoped: Support for both global application state and scoped feature state.
  • Zero Mismatch: Designed to avoid hydration warnings by matching server and client initial renders exactly.

Installation

Add this to your Cargo.toml:

[dependencies]
leptos_hydrated = "0.4"

Quick Start

1. Define your State with Hydratable

The most robust way to use leptos_hydrated is by implementing the Hydratable trait. This encapsulates your synchronous "seed" logic (e.g., cookies) and your asynchronous "refresh" logic (e.g., API calls).

use leptos::prelude::*;
use leptos_hydrated::*;
use serde::{Serialize, Deserialize};

#[derive(Clone, Default, Serialize, Deserialize, PartialEq, Debug)]
pub struct ThemeState {
    pub theme: String,
}

impl Hydratable for ThemeState {
    fn initial() -> Self {
        // This runs on both server and client to "seed" the state.
        // Usually read from a cookie or URL parameter.
        ThemeState { theme: "dark".into() }
    }

    async fn fetch() -> Result<Self, ServerFnError> {
        // This runs ONLY on the client to refresh the state with full data.
        Ok(ThemeState { theme: "light".into() })
    }
}

2. Choose your hydration strategy

HydrateState (Global State)

Provides global state via context. Place it anywhere in your view tree.

#[component]
pub fn App() -> impl IntoView {
    view! {
        // Initialize global theme state
        <HydrateState<ThemeState> />
        
        <MainContent />
    }
}

#[component]
fn MainContent() -> impl IntoView {
    // Access the hydrated signal anywhere
    let state = use_hydrated::<ThemeState>();
    view! {
        <p>"Theme: " {move || state.get().theme}</p>
    }
}

HydrateContext (Scoped State)

Provides scoped state to a specific branch of the component tree.

#[component]
fn ProfileSection() -> impl IntoView {
    view! {
        <HydrateContext<UserState>>
            <ProfileInfo />
        </HydrateContext<UserState>>
    }
}

3. Manual Hydration (Advanced)

If you don't want to use the trait, you can use the base components directly:

view! {
    // Global
    <HydrateStateWith
        ssr_value=|| ThemeState { theme: "dark".into() }
        fetcher=|| async { Ok(ThemeState { theme: "light".into() }) }
    />
    
    // Scoped
    <HydrateContextWith
        ssr_value=|| ThemeState { theme: "dark".into() }
        fetcher=|| async { Ok(ThemeState { theme: "light".into() }) }
    >
        <ProfileInfo />
    </HydrateContextWith>
}

Why use this instead of a standard Resource?

Standard Leptos Resources are fantastic for data that lives on the server and needs to be serialized to the client. However, they can cause "flickers" or require Suspense masks for data you already have on both sides (like a cookie).

leptos_hydrated allows you to:

  1. Render immediately on the server using a synchronous value.
  2. Hydrate immediately on the client with that same value (no flicker!).
  3. Refresh in the background once the WASM is ready to get the latest data.

Documentation

Full API documentation is available at docs.rs/leptos_hydrated.