Expand description

This module contains implementations of common Rust collections that work well with Perseus’ reactive state platform. So that this is as extensible as possible, all the code in this module follows a general pattern, so that you can easily create your own implementations as necessary!

First, it is important to understand that each type has two versions: Collection and CollectionNested. The former works with elements that will be simply wrapped in RcSignals, while the latter expects its elements to implement MakeRx etc. This difference can be extremely useful for users, because sometimes you’ll want a vector of some reactive struct, while other times you’ll just want a vector of Strings.

Second, each reactive collection is a thin wrapper over the basic collection. For example, RxVecNested is defined as struct RxVecNested<T>(Vec<T>), with several constraints on T.

Third, each reactive collection has itself two types: RxCollection and RxCollectionRx. The former is the base, unreactive collections, while the latter is fully reactive. This is just like how the #[derive(ReactiveState)] macro creates an alias type MyStateRx for some state MyState. Note that these structs should have the same type bounds on T.

Fourth, the unreactive types will have to implement Serialize and Deserialize, from Serde. You can get this working by omitting the Serialize + DeserializeOwned bounds on T in the unreactive type definition, and by then letting the derive macros from Serde fill them in automatically. Note the use of DeserializeOwned in type bounds, which avoids lifetime concerns and HRTBs.

Finally, every file in this module follows the same code pattern, allowing maximal extensibility and self-documentation:

// --- Type definitions ---
// ...
// --- Reactivity implementations ---
// ...
// --- Dereferencing ---
// ...
// --- Conversion implementation ---
// ...
// --- Freezing implementation ---
// ...

The type definitions section contains the actual definitions of the reactive and unreactive collection types, while the reactivity implementations section contains the implementations of MakeRx for the unreactive type, and MakeUnrx for the reactive type.

The dereferencing section contains implementations that allow users to use the methods of the underlying collection on these wrapper types. For example, by implementing Deref with a target of Vec<T::Rx> for RxVecNestedRx<T>, users can take that reactive type and call methods like .iter() on it.

The conversion implementation section implements From for the unreactive type, allowing users to easily create the base unreactive type from the type it wraps (e.g. RxVecNested<T> from a Vec<T>). This is primarily used in functions like get_build_state, where users can create the normal Rust collection, and just add .into() to integrate it with the Perseus state platform. Note that we do not implement From for the reactive version, as this will never be of use to users (reactive types should only ever come out of Perseus itself, so they can be registered in the state store, etc.).

Finally, the freezing implementation section implements Freeze for the reactive type, which allows Perseus to turn it into a String easily for state freezing. Thawing is handled automatically and internally.

A brief note on RxResult<T, E>: the reactive result type does not follow the patterns described above, and it defined in a separate module, because it does not have a non-nested equivalent. This is because such a thing would have no point, as there are no ‘fields’ in a Result (or any other enum, for that matter) itself. If a non-nested version is required, one should simply use std::result::Result. The same goes for Option<T>, although there is presently no defined reactive container for this.

Note: as a user, you will still have to use #[rx(nested)] over any reactive types you use, even those that are not nested! This is because, without this attribute, the ReactiveState derive macro will just wrap the whole field in an RcSignal and call it a day, rather than using the fine-grained reactivity enabled by these types.

Also note: when iterating over any of these collections to create View fragments, you will need to use create_ref() to prevent lifetime errors, like so:

// Note the use of `create_ref()` here
let list = create_ref(cx, state.list.get());
let view = View::new_fragment(
    list.iter()
    .map(|elem| {
        // ...
            (elem.get())
        }
    })
    .collect()
);

view! { cx,
    (view)
}

Structs

  • A reactive version of Vec that uses nested reactivity on its elements. This requires nothing by Clone + 'static of the elements inside the map, and it wraps them in RcSignals to make them reactive. If you want to store nested reactive types inside the map (e.g. Strings), you should use super::RxHashMapNested.
  • A reactive version of HashMap that uses nested reactivity on its elements. That means the type inside the vector must implement MakeRx (usually derived with the ReactiveState macro). If you want to store simple types inside the vector, without nested reactivity (e.g. Strings), you should use super::RxHashMap.
  • The reactive version of RxHashMapNested.
  • The reactive version of RxHashMap.
  • A reactive version of Vec that uses nested reactivity on its elements. This requires nothing by Clone + 'static of the elements inside the vector, and it wraps them in RcSignals to make them reactive. If you want to store nested reactive types inside the vector (e.g. Strings), you should use super::RxVecNested.
  • A reactive version of Vec that uses nested reactivity on its elements. That means the type inside the vector must implement MakeRx (usually derived with the ReactiveState macro). If you want to store simple types inside the vector, without nested reactivity (e.g. Strings), you should use super::RxVec.
  • The reactive version of RxVecNested.
  • The reactive version of RxVec.