Leptos Book

repository·main·Indexed 41 days ago

https://github.com/leptos-rs/book

An introductory guide to the Leptos framework, a fine-grained reactive Rust web framework. It covers Client-side rendering (CSR) with Trunk, Server-side rendering (SSR) with cargo-leptos, reactivity via Signals and Effects, and deployment best practices. The documentation includes guides on using the Leptos Router, implementing progressive enhancement, and integrating with server runtimes like Axum and Actix.

Tokens
64.8K
Snippets
168
Records
232
Agent score
52%

What's inside Leptos

  1. Introduction to the Leptos Web Framework

    main

    Leptos is a Rust-based web framework designed for building high-performance applications. It utilizes fine-grained reactivity and supports a progression from simple client-side browser rendering to full-stack applications featuring server-side rendering (SSR) and hydration.

    Prerequisites for using Leptos:

    • Proficiency in the Rust programming language.
    • Familiarity with HTML, CSS, the DOM, and basic Web APIs.

    Conceptual Mental Models: Leptos is conceptually similar to:

    • SolidJS (JavaScript)
    • Sycamore (Rust)

    It shares some characteristics with React, Svelte, Yew, and Dioxus, though its core mechanism is fine-grained reactivity rather than a virtual DOM.

    For exhaustive technical details on every function and type, refer to the official Docs.rs documentation.

  2. Overview of Server-Side Capabilities in Leptos

    main
    Leptos allows you to write code that runs exclusively on the server within the same application, using the same language (Rust) and sharing the same types. While server-side rendering (SSR) handles generating initial HTML, the server can also perform tasks that are impossible in the browser, such as direct interaction with SQL databases. Unlike traditional JavaScript frontend development that requires separate REST APIs, Leptos enables you to define server-side logic and client-side interactivity in the same files, bridging the gap between the two environments.
  3. Benefits of using Islands Architecture

    main

    Using Islands mode in Leptos provides several advantages over standard client-side hydration:

    • Reduced WASM Binary Size: Only the code for #[island] components is shipped to the client. Non-interactive parts of the page remain as inert HTML.
    • Lower Data Serialization Costs: Avoids "double data" (where data is both rendered to HTML and serialized as JSON for hydration) by keeping much of the content server-only.
    • Direct Server API Access: Components inside a #[component] (that are not islands) run exclusively on the server, allowing direct use of server-only APIs like std::fs without needing #[server] functions or Suspense boilerplate.
  4. What is a derived signal and how to use it

    main

    A derived signal is a closure that accesses one or more signals to create a reactive computed value. This allows you to encapsulate logic and reuse the result in multiple places in your view.

    When a derived signal is used in a view!, it runs the calculation once per signal change and once per location it is accessed. For expensive calculations, use memos (covered in a later chapter) instead of derived signals to avoid redundant computation.

    let (count, set_count) = signal(0);
    
    // Define the derived signal
    let double_count = move || count.get() * 2;
    
    view! {
        <progress
            max="50"
            value=double_count
        />
        <p>
            "Double Count: "
            {double_count}
        </p>
    }
  5. What is the Islands Architecture in Leptos

    main

    The Islands Architecture in Leptos inverts the traditional Single Page Application (SPA) model. Instead of the entire application being interactive and requiring the full app to be hydrated in the browser, the app starts as a 'sea' of static, server-rendered HTML. Interactivity is added only to specific, small areas called 'islands'.

    Key Benefits:

    • Reduced WASM Binary Size: Only the code required for the islands is compiled to WASM and shipped to the client. Non-interactive content (regular #[component]s) does not increase the client-side bundle size.
    • Improved Performance: Smaller bundles lead to faster downloads and hydration.
    • Server-First Components: Code inside #[component] functions runs only on the server unless that component is used inside an #[island].
  6. How controlled inputs work in Leptos

    main

    In a controlled input, the framework manages the input's state. On every on:input event, a signal is updated, and that signal is used to update the input's DOM property.

    Key technical details:

    1. Use on:input for real-time updates (fires on every keystroke) or on:change for updates when the input loses focus.
    2. Use prop:value (or prop:checked for checkboxes) instead of the value attribute. The value attribute only sets the initial value, whereas the value property controls the current value as the user types.
    3. To access the event target in a typed manner, use the on:input:target syntax.
    let (name, set_name) = signal("Controlled".to_string());
    
    view! {
        <input type="text"
            // :target gives typed access to the event target
            on:input:target=move |ev| {
                // .value() returns the current value of the HTML input
                set_name.set(ev.target().value());
            }
    
            // Use prop: to update the DOM property, not the attribute
            prop:value=name
        />
        <p>"Name is: " {name}</p>
    }
  7. Use the `<Form/>` component for client-side navigation

    main

    The <Form/> component from leptos_router behaves like a standard HTML <form> but performs client-side navigation instead of full page reloads. This allows you to update the URL and application state without a round-trip to the server.

    Supported Methods

    • method="GET": Navigates to the URL defined in the action attribute, encoding form input data into the URL query string. This is ideal for search interfaces where you want the search state to be shareable via a URL.
    • method="POST": Makes a POST request to the action URL and handles the server's response.

    Key Benefits

    • URL Synchronization: By using GET forms, the application state (like search queries) is stored directly in the URL. This makes the application bookmarkable and shareable.
    • Reactive Data Flow: When a <Form method="GET" /> is submitted, the URL changes, which can trigger reactive updates via hooks like use_query_map(), subsequently reloading Resources driven by those query parameters.
    // Example of a GET form for search
    <Form method="GET" action="">
        <input type="search" name="q" value=search/>
        <input type="submit"/>
    </Form>
  8. How nested routing works in Leptos

    main

    In Leptos, nested routing is a mechanism for creating layouts where multiple routes are rendered simultaneously on the same page. Instead of a URL matching exactly one route, a nested URL can match multiple routes in a hierarchy.

    When you use <ParentRoute/>, the parent component acts as a layout. If a child route matches, both the parent component and the child component are rendered at the same time.

    Key behaviors:

    • A URL like /users/3 can match both a /users parent route and a :id child route.
    • To handle the base path of a parent (e.g., /users without an ID), you should provide a route with an empty path path!("") inside the <ParentRoute/>.
    <Routes fallback=|| "Not found.">
      <ParentRoute path=path!("/users") view=Users>
        <Route path=path!(":id") view=UserProfile/>
        <Route path=path!("") view=NoUser/>
      </ParentRoute>
    </Routes>
  9. How uncontrolled inputs work in Leptos

    main

    In an uncontrolled input, the browser manages the input's state. Instead of updating a signal on every keystroke, you use a NodeRef to pull the value from the DOM element only when needed (e.g., during a form submission).

    Implementation details:

    1. Create a NodeRef<html::Input> (or the appropriate element type from leptos::html).
    2. Attach it to the element using node_ref=....
    3. Use the value attribute (not prop:value) to set the initial value.
    4. Access the value by calling .get() on the NodeRef, which returns an Option<leptos::HtmlElement<T>>. This element implements Deref to a web_sys type, allowing you to call methods like .value().
    let (name, set_name) = signal("Uncontrolled".to_string());
    let input_element: NodeRef<html::Input> = NodeRef::new();
    
    view! {
        <form on:submit=move |ev| {
            ev.prevent_default();
            let val = input_element.get().expect("mounted").value();
            set_name.set(val);
        }>
            <input type="text" value="Uncontrolled" node_ref=input_element />
            <input type="submit" value="Submit"/>
        </form>
    }
  10. Projecting children through multiple component layers

    main

    When building components, you may want to "project" children through multiple layers (e.g., passing children from a parent component down to a deeply nested child).

    If you attempt to move children or fallback props directly into nested components that use ChildrenFn (like <Show/> or <Suspense/>), you will encounter a compilation error: expected a closure that implements the Fn trait, but this closure only implements FnOnce. This happens because components like <Show/> need to call their children multiple times, but moving the props into the first layer of children consumes them, making them unavailable for subsequent calls.

    To solve this, use StoredValue to hand ownership of the props to the reactive system. This allows you to access them via a Copy and 'static reference that can be called multiple times.

    pub fn NestedShow<F, IV>(fallback: F, children: ChildrenFn) -> impl IntoView
    where
        F: Fn() -> IV + Send + Sync + 'static,
        IV: IntoView + 'static,
    {
        let fallback = StoredValue::new(fallback);
        let children = StoredValue::new(children);
    
        view! {
            <Show
                when=|| todo!()
                fallback=|| ()
            >
                <Show
                    when=move || todo!()
                    fallback=move || fallback.read_value()()
                >
                    {children.read_value()()}
                </Show>
            </Show>
        }
    }
  11. How automatic and dynamic dependency tracking works

    main

    Leptos uses a synchronous reactive model that eliminates the need for manual dependency arrays (unlike React).

    1. Automatic: The framework tracks which signals are accessed during the effect's execution and handles subscriptions automatically.
    2. Dynamic: Dependencies are recalculated on every run. If an effect contains logic like if use_last.get() { last.get() }, the effect will only subscribe to last when use_last is true. When use_last becomes false, last is removed from the dependency list. This ensures the effect reruns the absolute minimum number of times.
    let (first, set_first) = signal(String::new());
    let (last, set_last) = signal(String::new());
    let (use_last, set_use_last) = signal(true);
    
    // this will add the name to the log
    // any time one of the source signals changes
    Effect::new(move |_| {
        logging::log!(
            "{}", if use_last.get() {
                format!("{} {}", first.get(), last.get())
            } else {
                first.get()
            },
        )
    });
  12. How Client-Side Navigation works in Leptos

    main

    Once the initial page has been hydrated, Leptos transitions from a server-rendered "multi-page app" to a browser-rendered "single-page app" (SPA).

    When a user clicks a link to navigate to a different route within the app, the WASM version of the application intercepts the navigation. Instead of making a full round-trip request to the server for a new HTML page, the client-side router loads the new component tree directly in the browser. This results in much faster secondary navigations compared to traditional server-side rendering.