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 Signal
s 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.
Match | Replacement |
---|---|
cx: Scope, | |
cx: Scope | |
cx, | |
cx | |
<'a, | < |
<'a> | |
&'a Signal | Signal |
&'a ReadSignal | ReadSignal |
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:
Match | Replacement |
---|---|
RcSignal | Signal |
create_rc_signal | create_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 Vec
s 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)
}
}