github leptos-rs/leptos v0.1.0

latest releases: 0.7.0-alpha, v0.6.12, v0.7.0-preview2...
17 months ago

Finally, here's the release for version 0.1.0. Thanks to everyone who's been working with the alpha, beta, and bleeding-edge versions over the last few weeks to help get everything ready.

Most of the release notes below are copied from the 0.1.0-alpha release, but include a few updates. All these changes are relative to 0.0.22.

Features

More flexible rendering

You can now easily mix and match different types of node in a Fragment, which previously needed to be a Vec of all the same types (like a Vec<Fragment>).

view! { cx,
  <>
    "This is some text."
    <p>"Here's an element."</p>
    <ComponentA/>
  </>
}

In fact, the <> fragment itself is optional.

view! { cx,
  "This is some text."
  <p>"Here's an element."</p>
  <ComponentA/>
}

Typed HTML elements

HTML elements generated by the view! macro are now typed, and implement Deref to give a correctly-typed web_sys element.

// returns `HtmlElement<Input>`
// derefs to a `web_sys::HtmlInputElement`
view! { cx, <input type="text"/> }

store_value

You can now store non-reactive values in the reactive system to take advantage of the same Copy + 'static reference system without the overhead or abstraction leak of storing something in a signal. This is useful for objects you need to reference in multiple places throughout your application without cloning.

// this structure is neither `Copy` nor `Clone`
pub struct MyUncloneableData {
  pub value: String
}

// ✅ you can move the `StoredValue` into closures and access it with .with()
let data = store_value(cx, MyUncloneableData { value: "a".into() });
let callback_a = move || data.with(|data| data.value == "a");
let callback_b = move || data.with(|data| data.value == "b");

Signal<T> and SignalSetter<T> abstractions

These wrappers allow you to define APIs that can take any kind of signal. For example, Signal<T> can be a ReadSignal, RwSignal, Memo, or derived signal. This is especially useful when defining components interfaces. (There's even a MaybeSignal for types that may just be T or may be a Signal<T>.)

let (count, set_count) = create_signal(cx, 2);
let double_count = Signal::derive(cx, move || count() * 2);
let memoized_double_count = create_memo(cx, move |_| count() * 2);

// this function takes any kind of wrapped signal
fn above_3(arg: &Signal<i32>) -> bool {
  // ✅ calling the signal clones and returns the value
  //    it is a shorthand for arg.get()
  arg() > 3
}

assert_eq!(above_3(&count.into()), false);
assert_eq!(above_3(&double_count), true);
assert_eq!(above_3(&memoized_double_count.into()), true);

Massively improved Rust Analyzer support

DX with this new system is much better. You'll see hugely improved language-server integration for the view! macro, including features like doc comments for HTML elements, autocompletion for component properties, clicking to go to the definition of a component, type hints for component arguments, etc.

Inline documentation in the #[component] macro

You can now document components and their properties directly in the #[component] macro like this:

/// A simple counter component.
/// 
/// You can use doc comments like this to document your component.
#[component]
pub fn SimpleCounter(
  cx: Scope,
  /// The starting value for the counter
  #[prop(optional)]
  initial_value: i32,
  /// The change that should be applied each time the button is clicked.
  step: i32
) -> impl IntoView {
  // ...
}

The macro will automatically generate docs for the components and for each of its props.

#[prop] attributes for component props

You can use the #[prop] attribute macro to mark component props in several ways:

  • #[prop(into)]: This will call .into() on any value passed into the component prop. (For example,
    you could apply #[prop(into)] to a prop that takes Signal, which would
    allow users to pass a ReadSignal or RwSignal
    and automatically convert it.)
  • #[prop(optional)]: If the user does not specify this property when they use the component,
    it will be set to its default value. If the property type is Option<T>, values should be passed
    as name=T and will be received as Some(T).
  • #[prop(optional_no_strip)]: The same as optional, but requires values to be passed as None or
    Some(T) explicitly. This means that the optional property can be omitted (and be None), or explicitly
    specified as either None or Some(T).
  • #[prop(default = X)]: The property is optional, and will be set to the default value provided if it is not passed.

tracing support

We've added support for debugging using the tracing crate, integrated into both the renderer and the reactive system. This means that when an effect or an event listener fires, you can use trace!(...) to get an insight into the flow of your program. See leptos_dom/examples/test-bench for an example setup; a separate crate to make this tracing setup easier will be forthcoming.

cargo-leptos integration improvements

cargo-leptos 0.1.2 and the Actix/Axum integrations in 0.1.0 now work together seamlessly, allowing you to create full-stack applications with as little boilerplate as possible.

And much more!

  • Comments that mark component boundaries for easier debugging, when in debug mode
  • Removing the need to wrap things that <Router/> in a <div> for no obvious reason
  • builder API that allows you to crate a UI without using the view macro
  • and all sorts of things I'm forgetting!

Breaking Changes

Here are a few things to watch out for as you migrate:

  • If you're using cargo-leptos already and run into any issues (for example, with the path of a JS or Wasm file), be sure to check out the latest examples and the starter. The extra structures here have changed a bit over time as we've tried to figure out the best patterns. Cloning the starter and adding your existing application code should work fine.
  • All component functions should now return -> impl IntoView
  • The <For/> component now takes a view property, instead of you passing the view function as the child of the component
  • Likewise, the <Route/> component takes a view property instead of element for consistency
  • children properties are now typed Box<dyn Fn(Scope) -> Fragment>
  • The view! macro returns distinct structures, which are all different types. In other words, this code no longer compiles
// this won't work
if some_condition {
  view! { cx, <p>"P if true"</p> }
} else {
  view! { cx, <span>"SPAN if false"</span> }
}

because the first branch now returns HtmlElement<P> while the second returns HtmlElement<Span>.

In this case, simply add .into_any() to each to return HtmlElement<AnyElement> from both. In general, you can always call .into_view(cx) to return a wrapping View type instead.

// this works
if some_condition {
  view! { cx, <p>"P if true"</p> }.into_any()
} else if something_else {
  view! { cx, <span>"SPAN if false"</span> }.into_any()
}

Don't miss a new leptos release

NewReleases is sending notifications on new releases.