Migration Guide: v0.8 to v0.9

Reactivity v3

Sycamore v0.9 introduces a brand-new reactivity system. Not only is the new reactivity system faster than before, it is much nicer to use. The biggest change is the removal of the explicit cx: Scope in favor of implicitly tracking scopes. This is just like v0.7, all the while still keeping the ergonomics of Copy-able Signals introduced in v0.8. This change also means that we no longer have to deal with lifetimes! Instead, everything is 'static, allowing signals to be copied easily into global event handlers among other things.

No more Scope!

Getting rid of all the cx: Scope from your code can be made much more efficient with a couple find and replace searches. Unfortunately, this will likely not work for all cases so you still need to go fix a few edge cases manually yourself.

MatchReplacement
cx: Scope,
cx: Scope
cx,
cx
<'a,<
<'a>
&'a SignalSignal
&'a ReadSignalReadSignal

No more RcSignal!

RcSignal has also been removed in favor of just using Signal which is now 'static. For the most part, this can be migrated using the following:

MatchReplacement
RcSignalSignal
create_rc_signalcreate_signal

Explicit .get_clone()

Another breaking change is that signals no longer automatically wrap their value inside an Rc. This means that for a non-Copy value, you will either need to clone the value or use the new helper method .with(|value| ...).

let number: Signal<i32> = create_signal(123);
let string: Signal<String> = create_signal("Hello, Sycamore!".to_string());

// `i32` implements `Copy`.
let _: i32 = number.get();
// `String` does not implement `Copy` but implements `Clone`.
let _: String = string.get_clone();

No more create_ref

Because of the how the new reactivity system is structured, we no longer provide a create_ref hook. Instead, if you need to pass around a Copy-able handle to some data, just use signals.

nightly only features

If you are using Rust nightly, you can enable the nightly feature on sycamore to be able to access the signal value by simply calling it:

// Stable
let value = signal.get();
// Nightly only
let value = signal();

For more information about the new reactivity system, check out the updated docs on Reactivity

View Backend v2

These pesky generics have been here since Sycamore v0.5 where we introduced SSR support. Up until now, we have used generics to allow Sycamore apps to be isomorphic, meaning that they can run on both the server and the client, rendering to a string and to the DOM respectively.

However, adding generics to every single function that returns a View quickly becomes tedious. No more! Now, Sycamore uses target detection to automatically select the right rendering backend. If it detects that we are building for a wasm32 target, the DOM backend will automatically be used. Otherwise, the SSR backend will be selected.

For updating your existing codebase, finding and replacing "<G: Html>" with "" (empty string) and "View<G>" with "View" should take care of most of the cases.

// Old
#[component(inline_props)]
fn Component<'a, G: Html'(cx: Scope<'a>, value: &'a ReadSignal<i32>) -> View<G> {
    ...
}

// New
#[component(inline_props)]
fn Component(value: ReadSignal<i32>) -> View {
    ...
}

New builder syntax

All attributes are correctly type-checked now. This also reflects itself in the builder API.

// Old
div()
    .c(h1()
        .t("Hello ")
        .dyn_if(
            move || !name.with(String::is_empty),
            move || span().dyn_t(move || name.get_clone()),
            move || span().t("World"),
        )
        .t("!"))
    .c(input().bind_value(name))
    .view();

// New
div()
    .children((
        h1().children((
            "Hello ",
            move || {
                if !name.with(String::is_empty) {
                    span().children(move || name.get_clone())
                } else {
                    span().children("World")
                }
            },
            "!",
        )),
        input().bind(bind::value, name),
    ))
    .into();

Refer to the page on the builder API in the book for more details.

Other small changes

There are a bunch of other smaller changes introduced by this release. Since so much of the core API has been updated, however, it is hard to provide a precise guide for migrating to v0.9 for all the changes. Some of them are documented below.

Event handlers now use the proper types

iterable renamed to list for Indexed and Keyed

// Old
view! {
    Indexed(
        iterable=...,
        view=...,
    )
}
// New
view! {
    Indexed(
        list=...,
        view=...,
    )
}

In addition, list now accepts static Vecs as well so no need creating a dummy signal when using Indexed/Keyed:

// Old
view! {
    Indexed(
        list=*create_signal(vec![...]),
        view=...,
    )
}
// New
view! {
    Indexed(
        list=vec![...],
        view=...,
    )
}

ref renamed to r#ref, type renamed to r#type

Since everything is based on the builder API now, the view! macro no longer special cases ref and type. Instead, use r#ref and r#type. All other identifiers that are Rust keywords should now be prepended with r#.

Views do not implement clone

Views no longer implement clone. This was somewhat of a footgun before because cloning a view creates a shallow clone. This has some weird consequences. It means that if you were to insert the same view twice, the view would be removed from the first location and inserted into the second location. This is rarely expected behavior.

In v0.9, this is no longer possible. Instead, for cases where you needed to clone views, consider using channels instead as this ensures that the view can only be used once.

Signals can be converted directly to views

No more need to do this:

view! {
    (signal.get())
}

Now, you can just write:

view! {
    (signal)
}

The resulting view is still, of course, dynamic.

Children can be converted directly to views

Children can now be directly interpolated inside the view! macro.

view! {
    div {
        (children)
    }
}