This version of the documentation is outdated, and features documented here may work differently now. You can see the latest stable version of the docs here.

State Amalgamation

In the introduction to this section, we mentioned that all these rendering strategies are compatible with one another, though we didn't explain how the two strategies that generate unique properties for a template can possible be compatible. That is, how can you use build state and request state in the same template? To our knowledge, Perseus is the only framework in the world (in any language) that supports using both, and it's made possible by state amalgamation, which lets you provide an arbitrary function that can merge conflicting states from these two strategies!

Usage

Here's an example from here:

use perseus::{RenderFnResultWithCause, Request, States, Template};
use sycamore::prelude::{view, Html, View};

#[perseus::make_rx(PageStateRx)]
pub struct PageState {
    pub message: String,
}

#[perseus::template_rx]
pub fn amalgamation_page(state: PageStateRx) -> View<G> {
    view! {
        p { (format!("The message is: '{}'", state.message.get())) }
    }
}

pub fn get_template<G: Html>() -> Template<G> {
    Template::new("amalgamation")
        // We'll generate some state at build time and some more at request time
        .build_state_fn(get_build_state)
        .request_state_fn(get_request_state)
        // But Perseus doesn't know which one to use, so we provide a function to unify them
        .amalgamate_states_fn(amalgamate_states)
        .template(amalgamation_page)
}

#[perseus::autoserde(amalgamate_states)]
pub fn amalgamate_states(states: States) -> RenderFnResultWithCause<Option<PageState>> {
    // We know they'll both be defined, and Perseus currently has to provide both as serialized strings
    let build_state = serde_json::from_str::<PageState>(&states.build_state.unwrap())?;
    let req_state = serde_json::from_str::<PageState>(&states.request_state.unwrap())?;

    Ok(Some(PageState {
        message: format!(
            "Hello from the amalgamation! (Build says: '{}', server says: '{}'.)",
            build_state.message, req_state.message
        ),
    }))
}

#[perseus::autoserde(build_state)]
pub async fn get_build_state(_path: String, _locale: String) -> RenderFnResultWithCause<PageState> {
    Ok(PageState {
        message: "Hello from the build process!".to_string(),
    })
}

#[perseus::autoserde(request_state)]
pub async fn get_request_state(
    _path: String,
    _locale: String,
    _req: Request,
) -> RenderFnResultWithCause<PageState> {
    Ok(PageState {
        message: "Hello from the server!".to_string(),
    })
}

This example illustrates a very simple amalgamation, taking the states of both strategies to produce a new state that combines the two. Note that this also uses RenderFnWithCause as a return type (see the section on the build state strategy for more information). It will be passed an instance of States, which you can learn more about in the API docs. As usual, serialization of your returned state is done with the #[perseus::autoserde(amalgamate_states)] macro, though the components of States will not be deserialized, and you'll have to do that manually. Note that the next major version of Perseus will deserialize the components of States automatically.