1 unstable release
Uses new Rust 2024
| 0.1.0 | Jun 4, 2025 |
|---|
#1882 in HTTP server
65KB
722 lines
leptos posthoc
Allows for "hydrating" an existent DOM with reactive leptos components, without the entire DOM having to be generated by leptos components.
Why would you want that?
- CSR: It allows for building scripts that others can just embed in their arbitrary HTML documents, that add
<insert your favourite fancy feature here>. For an example, see theexamples/csrdirectory: theindex.htmlhas a node<script src='csr_example.js'></script>, which "hydrates" nodes with thedata-replace-with-leptos-attribute with leptos components that add a hover-popup (using thaw). - SSR: Occasionally, you might want to dynamically insert some HTML string into the DOM, for example one that
gets generated from some data and returned by a server function. This HTML might contain certain nodes that
we want to attach reactive functionality to. For an example, see the
examples/ssrdirectory.
CSR Example
Say we want to replace all elements with the attribute data-replace-with-leptos with a leptos component
MyReplacementComponent, that simply wraps the original children in a div with a solid red border. This
component would roughly look like this:
#[component]
fn MyReplacementComponent(orig:OriginalNode) -> impl IntoView {
view! {
<div style="border: 1px solid red;">
<DomChildren orig />
</div>
}
}
This component takes an orig:OriginalNode that represents the, well, original Element.
So, where do we get orig from?
-
If we already have an
e:&Element, we can simply calle.into(). -
More likely, we don't have an
Elementyet. Moreover, we probably want to iterate over the entire body once to find all nodes we want to make reactive, and we also need to set up a global reactive system for all our inserted components.To do that, we call
hydrate_body(requires thecsrfeature flag) with a function that takes theOriginalNodeof the body and returns some leptos view; e.g.:
#[component]
fn MainBody(orig:OriginalNode) -> impl IntoView {
// set up some signals, provide context etc.
view!{
<DomChildren orig/>
}
}
#[wasm_bindgen(start)]
pub fn run() {
console_error_panic_hook::set_once();
hydrate_body(|orig| view!(<MainBody orig/>).into_any())
}
This sets up the reactive system, but does not yet replace any elements further down in the DOM. To do that,
we provide a function that takes an &Element and optionally returns an
FnOnce() -> impl IntoView+'static, if the element should be changed. This function is then passed to
DomChildrenCont, which will iterate over all children of the replaced element and replace them with the
provided function.
Let's modify our MainBody to replace all elements with the attribute data-replace-with-leptos with a
MyReplacementComponent:
fn replace(e:&Element) -> Option<impl FnOnce() -> AnyView> {
e.get_attribute("data-replace-with-leptos").map(|_| {
let orig: OriginalNode = e.clone().into();
|| view!(<MyReplacementComponent orig/>).into_any()
})
}
#[component]
fn MainBody(orig:OriginalNode) -> impl IntoView {
// set up some signals, provide context etc.
view!{
<DomChildrenCont orig cont=replace/>
}
}
#[component]
fn MyReplacementComponent(orig:OriginalNode) -> impl IntoView {
view! {
<div style="border: 1px solid red;">
<DomChildrenCont orig cont=replace/>
</div>
}
}
...now, replace will get called on every element of the DOM, including those that were "moved around" in
earlier MyReplacementComponents, respecting the proper reactive graph (regardin signal inheritance etc.).
SSR Example
In general, for SSR we can simply use the normal leptos components to generate the entire DOM. We control the server, hence we control the DOM anyway.
However, it might occasionally be the case that we want to dynamically extend the DOM at some point by
retrieving HTML from elsewhere, and then want to do a similar "hydration" iteration over the freshly inserted
nodes. This is what DomStringCont is for, and it does not require the csr feature:
#[component]
fn MyComponentThatGetsAStringFromSomewhere() -> impl IntoView {
// get some HTML string from somewhere
// e.g. some API call
let html = "<div data-replace-with-leptos>...</div>".to_string();
view! {
<DomStringCont html cont=replace/>
}
}
See the examples/ssr directory for a full example.
Dependencies
~23–31MB
~581K SLoC