Skip to content

Releases: leptos-rs/leptos

v0.5.1

06 Oct 14:02
@gbj gbj
Compare
Choose a tag to compare

This is primarily a bugfix release, but it also includes a new feature: making the fallback prop on Show, Suspense, and Transition optional.

For 0.5 release notes in general, click here.

What's Changed

  • docs: fix For view prop name (closes #1813) by @gbj in #1814
  • Missing docs and Copy impl for Callback by @gbj in #1818
  • Fix template! cfg condition by @Dragonink in #1822
  • Docs: DX improvements: add section about jetbrains intellij-rust by @sebadob in #1804
  • made show fallback optional by @maccesch in #1817
  • Fixes #1828, making SSR behavior match the Hydrate behavior. by @dgsantana in #1829
  • fix: make explicit allowances for local-only suspense fragments in SSR/hydrate mode (closes #1823) by @gbj in #1824
  • fix: correctly quote spread attributes in {..attrs} syntax (fixes #1826) by @gbj in #1831
  • Allow disposing of Signal & StoredValue by @PaulWagener in #1849
  • Make Async Mode return Content-Type header in Response by @benwis in #1851
  • feat: support stored values in with! and update! by @blorbb in #1836
  • Removed warning in build artefacts. by @martinfrances107 in #1840
  • fix: update log debug to use get_untracked for logged in user to resolve client side console error by @kevinold in #1834
  • fix: panic during generate_route_list if you immediately dispatch an action (closes #1832) by @gbj in #1853
  • fix: clippy "needless lifetimes" warning (closes #1825) by @gbj in #1852
  • "#two" was being hidden. Added another # to unhide by @he00741098 in #1847

New Contributors

Full Changelog: v0.5.0...v0.5.1

v0.5.0

29 Sep 21:35
@gbj gbj
fa2be59
Compare
Choose a tag to compare

v0.5.0

Goodbye, Scope

This long-awaited release changes the nature of the reactive system by removing the entire concept of explicit Scope and the ubiquitous cx variable.

The primary impetus behind this change is that it increases the correctness of the behavior of the reactive system, and fixes several persistent issues.

Click here for more details. In order to provide signals that implement `Copy` are are `'static` and are therefore easy to use with closures and event listeners in 100% safe Rust, Leptos allocates memory for signals, memos, and effects in an arena. This raises the question: When is it safe to deallocate/dispose of these signals?

From 0.0 to 0.4, Leptos allocated signals in a dedicated Scope, which was ubiquitous in APIs. This had several drawbacks

  1. Ergonomics: It was annoying additional boilerplate to pass around.
  2. Trait implementations: Needing an additional Scope argument on many functions prevented us from implementing many traits that could not take an additional argument on signals, like From, Serialize/Deserialize.
  3. Correctness: Two characteristics made this system somewhat broken
  • The Scope was stored in a variable that was passed around, meaning that the β€œwrong” scope could be passed into functions (most frequently Resource::read()). If, for example, a derived signal or memo read from a resource in the component body, and was called under a Suspense lower in the tree, the Scope used would be from the parent component, not the Suspense. This was just wrong, but involved wrapping the function in another closure to pass in the correct Scope.
  • It was relatively easy to create situations, that could leak memory unless child Scopes were manually created and disposed, or in which on_cleanup was never called. (See #802 and #918 for more background.)

The solution to this problem was to do what I should have been doing a year ago, and merge the memory allocation function of Scope into the reactive graph itself, which already handles reactive unsubscriptions and cleanup. JavaScript doesn’t deal with memory management, but SolidJS handles its onCleanup through a concept of reactive ownership; disposing of memory for our signals is really just a case of cleanup on an effect or memo rerunning.

Essentially, rather than being owned by a Scope every signal, effect, or memo is now owned by its parent effect or memo. (If it’s in an untrack, there’s no reactive observer but the reactive owner remains.) Every time an effect or memo reruns, it disposes of everything β€œbeneath” it in the tree. This makes sense: for a signal to be owned by an effect/memo, it must have been created during the previous run, and will be recreated as needed during the next run, so this is the perfect time to dispose of it.

It also has the fairly large benefit of removing the need to pass cx or Scope variables around at all, and allowing the implementation of a bunch of different traits on the various signal types.

Now that we don't need an extra Scope argument to construct them, many of the signal types now implement Serialize/Deserialize directly, as well as From<T>. This should make it significantly easier to do things like "reactively serialize a nested data structure in a create_effect" β€” this removed literally dozens of lines of serialization/deserialization logic and a custom DTO from the todomvc example. Serializing a signal simply serializes its value, in a reactive way; deserializing into a signal creates a new signal containing that deserialized value.

Migration is fairly easy. 95% of apps will migrate completely by making the following string replacements:

  1. cx: Scope, => (empty string)
  2. cx: Scope => (empty string)
  3. cx, => (empty string)
  4. (cx) => ()
  5. |cx| => ||
  6. Scope, => (empty string)
  7. Scope => (empty string) as needed
  8. You may have some |_, _| that become |_| or |_| that become ||, particularly for the fallback props on <Show/> and <ErrorBoundary/>.

Basically, there is no longer a Scope type, and anything that used to take it can simply be deleted.

For the 5%: if you were doing tricky things like storing a Scope somewhere in a struct or variable and then reusing it, you should be able to achieve the same result by storing Owner::current() somewhere and then later using it in with_owner(owner, move || { /* ... */ }). If you have issues with this kind of migration, please let me know by opening an issue or discussion thread and we can work through the migration.

Islands

This release contains an initial, but very functional, implementation of the β€œislands architecture” for Leptos.

This adds an experimental-islands feature that opts you into a rendering mode that's different from the traditional server-rendered/client-hydrated model described here, in which the entire page requested is rendered to HTML on the server for the first request, and then runs on the client to hydrate this server-rendered HTML, and subsequent navigations take place by rendering pages in the browser.

The experimental- in experimental-islands means β€œparts of this API may need to change, and I won’t guarantee that its APIs won’t break during 0.5,” but I have no reasons to believe there are significant issues, and I have no planned API changes at present.

With this islands feature on, components are only rendered on the server, by default. Navigations follow the traditional/multi-page-app (MPA) style of navigating by fetching a new HTML page from the server. The name "islands" comes from the concept of the "islands architecture," in which you opt into small β€œislands” of interactivity in an β€œocean” of server-rendered, non-interactive HTML.

This allows reducing the WASM binary size by a lot (say ~80% for a typical demo app), making the time-to-interactive for a page load much faster. It also allows you to treat most components as β€œserver components” and use server-only APIs, because those plain components will never need to be rendered in the browser.

I still need to write some guide-style docs for the book, but I tried to put a pretty good amount of information in the PR, which you should read if you’re interested in this topic or in trying out islands.

There’s significant additional exploration that will take place in this direction. Expect a longer treatment in an upcoming updated roadmap posted as a pinned issue.

Additional Reading

Static Site Generation

This release includes some preliminary work on building static site rendering directly into the framework, mostly as part of leptos_router and the server integrations. Static site generation is built off of two key components, a new <StaticRoute /> component and the leptos_router::build_static_routes function.

StaticRoute defines a new static route. In order to be a static route, all parent routes must be static in order to ensure that complete URLs can be built as static pages. A β€œstatic route” means that a page can be rendered as a complete HTML page that is then cached and served on subsequent requests, rather than dynamically rendered, significantly reducing the server’s workload per request.

StaticRoute takes a path and view (like any Route) and a static_params prop. This is a function that returns a Future, which is used to provide a map of all the possible values for each route param. These static params are generated on the server. This means you can do something like call a #[server] function or access a database or the server filesystem. For example, if you have a /post/:id path, you might query the database for a list of all blog posts and use that to generate a set of possible params.

StaticRoute can be given an optional mode: StaticMode::Upfront (the default) or StaticMode::Incremental. Upfront means that all given routes will be generated when you call build_static_routes, and a 404 will be returned for any other pages. Incremental means that all the options you give in static_params will be generated up front, and additional pages will be generated when first requested and then cached.

Where our routes are defined, we can include a StaticRoute:

view! {
	<Routes>
		<StaticRoute
			mode=StaticMode::Incremental
			path="/incr/:id"
			view=StaticIdView
			static_params=move || Box::pin(async move {
				let mut map = StaticParamsMap::default();
				map.insert("id".to_string(), vec![(1).to_string(), (2).to_string()]);
				map
			})
		/>
	</Routes>
}

In main.rs, we build the static routes for the site on server startup:

let (routes, static_data_map) = generate_route_list_with_ssg(App);
build_static_routes(&conf.leptos_options, App, &routes, &static_data_map)
	.await
	.unwrap();

More to Come

There is additional work needed here as far as providing examples, and building out the story for things like invalidation (when to rebuild a page) and build hooks (when to build additional pages).

Other New Features in 0.5

attr: on components, and spreading attributes

Makes it much easier to pass some set of attributes to be given to a component (with attr: passed into a #[prop(attrs)] prop), and then to spread them onto an element with {..attrs} syntax.

#[...
Read more

v0.5.0-rc3

22 Sep 22:00
@gbj gbj
d99269a
Compare
Choose a tag to compare
v0.5.0-rc3 Pre-release
Pre-release

See here for more substance.

Most significant breaking change you might notice is that generate_route_list in Axum is no longer async and doesn't need .await, as is already true for Actix.

Big features including static site generation included here, but I will wait for an actual release to write up notes because it's Friday afternoon!

What's Changed (since v0.5.0-rc2)

New Contributors

Full Changelog: v0.5.0-rc2...v0.5.0-rc3

v0.5.0-rc2

16 Sep 20:08
@gbj gbj
Compare
Choose a tag to compare
v0.5.0-rc2 Pre-release
Pre-release

See here for substance.

What's Changed

  • build(examples): make it easier to run examples by @agilarity in #1697
  • fix: a few small fixes to rc1 by @gbj in #1704
  • fix: impl IntoView for Rc<dyn Fn() -> impl IntoView> by @Baptistemontan in #1698
  • feat: impl From<HtmlElement<El>> for HtmlElement<AnyElement> by @jquesada2016 in #1700
  • doc(examples): reference run instructions by @agilarity in #1705
  • feat: Callbacks: Manual Clone and Debug impl; Visibility fix by @lpotthast in #1703
  • feat: impled LeptosRoutes for &mut ServiceConfig in integrations/actix by @cosmobrain0 in #1706
  • docs: update possibly deprecated docs for component macro by @ChristopherPerry6060 in #1696
  • fix: exclude markdown files from examples lists by @agilarity in #1716
  • fix(examples/build): do not require stop to end trunk by @agilarity in #1713
  • docs: fix a typo that prevented the latest appendix from appearing in the book by @g2p in #1719
  • feat: support move on with! macros by @blorbb in #1717
  • feat: implement Serialize and Deserialize for Oco<_> by @gbj in #1720
  • fix: document #[prop(default = ...)] as in Optional Props (closes #1710) by @gbj in #1721
  • fix: correctly register Resource::with() (closes #1711) by @gbj in #1726
  • fix: replace uses of create_effect internally with create_isomorphic_effect (closes #1709) by @gbj in #1723
  • fix: relax bounds on LeptosRoutes by @ChristopherPerry6060 in #1729
  • feat: Allow component names to be paths by @mrvillage in #1725
  • feat: use attr: syntax rather than AdditionalAttributes by @gbj in #1728

New Contributors

Full Changelog: v0.5.0-rc1...v0.5.0-rc2

v0.5.0-rc1

12 Sep 02:04
@gbj gbj
Compare
Choose a tag to compare
v0.5.0-rc1 Pre-release
Pre-release

I'm planning on releasing 0.5.0 proper toward the end of this week. However, we've continued having some really good new features and some changes, so I want to give it some additional time for testing rather than rushing and breaking things.

For overall 0.5.0 changes, see here.

New Features in this Release

attr: on components, and spreading attributes

Makes it much easier to pass some set of attributes to be given to a component (with attr: passed into a #[prop(attrs)] prop), and then to spread them onto an element with {..attrs} syntax.

#[component]
pub fn App() -> impl IntoView {
    view! {
        <Input attr:value="hello" attr:label="foo" />
        <Input attr:type="number" attr:value="0" />
    }
}

#[component]
pub fn Input(
    #[prop(attrs)] attrs: Vec<(&'static str, Attribute)>,
) -> impl IntoView {
    view! {
        <input {..attrs} />
        <pre>{format!("{attrs2:#?}")}</pre>
    }
}

Generics on components in view

Newly added support for syntax that identifies a generic type for a component in the view

#[component]
pub fn GenericComponent<S>(#[prop(optional)] ty: PhantomData<S>) -> impl IntoView {
    std::any::type_name::<S>()
}

#[component]
pub fn App() -> impl IntoView {
    view! {
        <GenericComponent<String>/>
        <GenericComponent<usize>/>
        <GenericComponent<i32>/>
    }
}

Callback types

These make it easier to add things like optional callback functions to components.

#[component]
pub fn App() -> impl IntoView {
    view! {
        <ShowOptFall when=|| { 5 < 3 } fallback=|_| view! { <p>"YES"</p> }>
            <p>"NO"</p>
        </ShowOptFall>
        <ShowOptFall when=|| { 5 < 3 }>
            <p>"NO"</p>
        </ShowOptFall>
        <ShowOptFall when=||{ 5 > 3 }>
            <p>"YES"</p>
        </ShowOptFall>
    }
}

#[component]
pub fn ShowOptFall<W>(
    /// The components Show wraps
    children: Box<dyn Fn() -> Fragment>,
    /// A closure that returns a bool that determines whether this thing runs
    when: W,
    /// A closure that returns what gets rendered if the when statement is false
    #[prop(optional, into)]
    fallback: Option<ViewCallback<()>>,
) -> impl IntoView
where
    W: Fn() -> bool + 'static,
{
    let memoized_when = create_memo(move |_| when());

    move || match memoized_when.get() {
        true => children().into_view(),
        false => match fallback.as_ref() {
            Some(fallback) => fallback.call(()).into_view(),
            None => ().into_view(),
        },
    }
}

with!() and update!() macros

Nested .withI() calls are a pain. Now you can do

let (first, _) = create_signal("Bob".to_string());
let (middle, _) = create_signal("J.".to_string());
let (last, _) = create_signal("Smith".to_string());
let name = move || with!(|first, middle, last| format!("{first} {middle} {last}"));

instead of

let name = move || {
	first.with(|first| {
		middle.with(|middle| last.with(|last| format!("{first} {middle} {last}")))
	})
};

Rustier interfaces for signal types

This framework's origins as a Rust port of SolidJS mean we've inherited some functional-JS-isms. Combined with the need to pass cx everywhere prior to 0.5 this has tended to mean we've gone with create_ and so on rather than Rusty ::new(). This simply adds a few Rustier constructors like

let count = RwSignal::new(0);
let double_count = Memo::new(move |_| count() * 2);

Breaking Changes

  • Renaming .derived_signal() and .mapped_signal_setter() to the more idiomatic .into_signal() and .into_signal_setter()
  • Memoizing whether Suspense is ready yet or not in order to avoid over-re-rendering. Shouldn't break your app, but if something weird happens let me know.
  • Changes timing of create_effect so that it runs a tick after it is created, which solves a number of timing issues with effects that had previously led to create_effect(move || request_animation_frame(move || /* */))

What's Changed

New Contributors

Read more

v0.5.0-beta2

30 Aug 11:03
@gbj gbj
Compare
Choose a tag to compare
v0.5.0-beta2 Pre-release
Pre-release

Just another beta release. I'm expecting to release 0.5.0 itself in the next couple weeks, there have just been a couple other small but breaking changes proposed that are working their way through, and I'd rather delay a bit than rush it and be stuck.

0.5.0-beta2 reflects the current state of the main branch. The v0.5.0 in General section is copied and pasted from previous releases. New this Release reflects changes since 0.5.0-beta (I think?).

New this Release

  • Make all arguments to #[server] optional: change default prefix to /api and default to generating a PascalCase type name from the function name
// before
#[server(MyName, "/api")]
pub async fn my_name() /* ... */
// after
#[server]
pub async fn my_name /* ... */
  • create_effect now returns an Effect struct. This exists mostly so you can .dispose() it early if you need. (This may require adding some missing semicolons, as create_effect no longer returns ().)
  • Support passing signals directly as attributes, classes, styles, and props on stable
  • Signal traits now take an associated Value type rather than a generic (see #1578)
  • Many APIs that previously took impl Into<Cow<'static, str>> now take impl Into<Oco<'static, str>>. Oco (Owned Clones Once) is a new type designed to minimized the cost of cloning immutable string types, like the ones used in the View. Essentially this makes it cheaper to clone text nodes and attribute nodes within the renderer, without imposing an additional cost when you don't need to. This shouldn't require changes to your application in most cases of normal usage. (See #1480 for additional discussion.)

v0.5.0 in General

Reactive System Changes

This long-awaited release significantly changes how the reactive system works. This should solve several correctness issues/rough edges that related to the manual use of cx/Scope and could create memory leaks. (See #918 for details)

It also has the fairly large DX change (improvement?) of removing the need to pass cx or Scope variables around at all.

Migration is fairly easy. 95% of apps will migrate completely by making the following string replacements:

  1. cx: Scope, => (empty string)
  2. cx: Scope => (empty string)
  3. cx, => (empty string)
  4. (cx) => ()
  5. |cx| => ||
  6. Scope, => (empty string)
  7. Scope => (empty string) as needed
  8. You may have some |_, _| that become |_| or |_| that become ||, particularly for the fallback props on <Show/> and <ErrorBoundary/>

Basically, there is no longer a Scope type, and anything that used to take it can simply be deleted.

For the 5%: if you were doing tricky things like storing a Scope somewhere in a struct or variable and then reusing it, you should be able to achieve the same result by storing Owner::current() somewhere and then later using it in with_owner(owner, move || { /* ... */ }).

There are no other big conceptual or API changes in this update: essentially all the same reactive, templating, and component concepts should still apply, just without cx.

What's Changed

  • change: shift from Scope-based ownership to reactive ownership by @gbj in #918
  • refactor(workflows): extract calls by @agilarity in #1566
  • refactor(verify-changed-examples): improve readability and runtime by @agilarity in #1556
  • Remove Clone requirement for slots in Vec by @Senzaki in #1564
  • fix: INFO is too high a level for this prop tracing by @gbj in #1570
  • fix: suppress warning about non-reactivity when calling .refetch() (occurs in e.g., async blocks in actions) by @gbj in #1576
  • fix: nightly warning in server macro for lifetime by @markcatley in #1580
  • fix: runtime disposal time in render_to_string_async by @gbj in #1574
  • feat: support passing signals directly as attributes, classes, styles, and props on stable by @gbj in #1577
  • feat: make struct name and path optional for server functions by @gbj in #1573
  • support effect dispose by @flisky in #1571
  • fix(counters_stable): restore wasm tests (#1581) by @agilarity in #1582
  • fix: fourth argument to server functions by @gbj in #1585
  • feat: signal traits should take associated types instead of generics by @gbj in #1578
  • refactor(check-stable): use matrix (#1543) by @agilarity in #1583
  • feat: add Fn traits for resources on nightly by @gbj in #1587
  • Convenient event handling with slots by @rkuklik in #1444
  • fix: correct logic for resource loading signal when read outside suspense (closes #1457) by @gbj in #1586
  • Update autoreload websocket connection to work outside of localhost by @rabidpug in #1548
  • Some resource and transition fixes by @gbj in #1588
  • fix: broken test with untrack in tracing props by @gbj in #1593
  • feat: update to typed-builder 0.16 (closes #1455) by @gbj in #1590
  • Oco (Owned Clones Once) smart pointer by @DanikVitek in #1480
  • docs: add docs for builder syntax by @gbj in #1603
  • chore(examples): improve cucumber support #1598 by @agilarity in #1599
  • fix(ci): add new webkit dependency (#1607) by @agilarity in #1608
  • fix(macro/params): clippy warning by @Maneren in #1612
  • Improve server function client side error handling by @drdo in #1597
  • Don't try to parse as JSON the result from a server function redirect by @JonRCahill in #1604
  • fix: add docs on #[server] functions by @realeinherjar in #1610

New Contributors

Full Changelog: v0.4.9...0.5.0-beta2

v0.4.9

20 Aug 18:44
@gbj gbj
Compare
Choose a tag to compare

I'm getting ready to merge the effect-cleanups branch into main, as well as merging #1480 and #1485 in preparation for the release of 0.5.0, so this release is just a "final state of 0.4.x" release before the main branch switches over from being 0.4 to 0.5. It does include some notable work, though:

  • refactored view macro with snapshot testing
  • improved hydration performance, avoiding browser work by reusing existing text nodes in certain cases
  • including component props in tracing support
  • many small bugfixes and improvements to docs

What's Changed

New Contributors

Full Changelog: v0.4.8...v0.4.9

v0.5.0-beta

08 Aug 00:24
@gbj gbj
Compare
Choose a tag to compare
v0.5.0-beta Pre-release
Pre-release

This beta release brings us closer to 0.5.0 proper.

The big changes are dropping cx/Scope entirely. I've copied and pasted the 0.5.0-alpha release notes below for reference, along with a few notes about other changes since the alpha release. A few other changes (#1480, #1485) will be incorporated once it's ready for release.

v0.5.0 in General

Reactive System Changes

This long-awaited release significantly changes how the reactive system works. This should solve several correctness issues/rough edges that related to the manual use of cx/Scope and could create memory leaks. (See #918 for details)

It also has the fairly large DX change (improvement?) of removing the need to pass cx or Scope variables around at all.

Migration is fairly easy. 95% of apps will migrate completely by making the following string replacements:

  1. cx: Scope, => (empty string)
  2. cx: Scope => (empty string)
  3. cx, => (empty string)
  4. (cx) => ()
  5. |cx| => ||
  6. Scope, => (empty string)
  7. Scope => (empty string) as needed
  8. You may have some |_, _| that become |_| or |_| that become ||, particularly for the fallback props on <Show/> and <ErrorBoundary/>

Basically, there is no longer a Scope type, and anything that used to take it can simply be deleted.

For the 5%: if you were doing tricky things like storing a Scope somewhere in a struct or variable and then reusing it, you should be able to achieve the same result by storing Owner::current() somewhere and then later using it in with_owner(owner, move || { /* ... */ }).

There are no other big conceptual or API changes in this update: essentially all the same reactive, templating, and component concepts should still apply, just without cx.

Other New Features

Now that we don't need an extra Scope argument to construct them, many of the signal types now implement Serialize/Deserialize directly, as well as From<T>. This should make it significantly easier to do things like "reactively serialize a nested data structure in a create_effect" β€” this removed literally dozens of lines of serialization/deserialization logic and a custom DTO from the todomvc example. Serializing a signal simply serializes its value, in a reactive way; deserializing into a signal creates a new signal containing that deserialized value.

Other Changes

use_navigate navigate function no longer returns a value

The navigate("path", options) call now uses request_animation_frame internally to delay a tick before navigating, which solves a few odd edge cases having to do with redirecting immediately and the timing of reactive system cleanups. This was a change that arose during 0.3 and 0.4 and is being made now to coincide with other breaking changes and a new version.

If you were relying on the Result<_, _> here and want access to it, let me know and we can bring back a version that has it. Otherwise, this shouldn't really require any changes to your app.

window_event_listener and friends now return a handle for removal

This one was just an API oversight originally, again taking advantage of the semver update. window_event_listener and its untyped version now return a WindowListenerHandle with a .remove() method that can be called explicitly to remove that event, for example in an on_cleanup.

Again, shouldn't require meaningful changes.

#[component]
fn TypingPage() -> impl IntoView {
    let handle = window_event_listener(ev::keypress, |ev| {
        /* do something */
    });
    on_cleanup(move || handle.remove());
}

Expected to Not Work

leptosfmt and cargo leptos --hot-reload, which assume that the view! needs a cx,, will probably not work as intended and will need to be updated.

Any other ecosystem libraries that depend on Leptos 0.4 won't work, of course.

v0.4.8

02 Aug 23:57
@gbj gbj
Compare
Choose a tag to compare

There was a log erroneously left in 0.4.7, so I rereleased as 0.4.8. Notes for 0.4.7 below.

This is mostly a bug-fix release with a few small features. There will probably be one or two more patch releases before 0.5.0.

New Features

mut in component props

This didn't work before; now it does:

#[component]
fn TestMutCallback<'a, F>(
    cx: Scope,
    mut callback: F,
    value: &'a str,
) -> impl IntoView
where
    F: FnMut(u32) + 'static,
{

MaybeProp type

MaybeProp is essentially a wrapper for Option<MaybeSignal<Option<T>>> with a nicer API. This is useful for something like a component library, where you want to give a user the maximum flexibility in the types they give to props while still checking that they're the appropriate T.

This allows you to define optional props that

  1. may or may not be provided
  2. if provided, may be a plain value or a signal
  3. if a signal type, may or may not have a value (i.e., are a Signal<Option<T>>)

Implementing From<_> for a plain Signal<T> type here is possible, but depends on the changes in 0.5.0, so I'll add it for that release.

#[component]
pub fn App(cx: Scope) -> impl IntoView {
    let (count, set_count) = create_signal(cx, 5);

    view! { cx,
        <TakesMaybeProp/>
        <TakesMaybeProp maybe=42/>
        <TakesMaybeProp maybe=Signal::derive(cx, move || Some(count.get()))/>
    }
}

#[component]
pub fn TakesMaybeProp(cx: Scope, #[prop(optional, into)] maybe: MaybeProp<i32>) -> impl IntoView {
    // .get() gives you `Option<T>`, replacing `self.as_ref().and_then(|n| n.get())`
    let value: Option<i32> = maybe.get();
    // .with() takes a plain `&T`; returns `None` if the prop isn't present or the inner value is None
    let plus_one = maybe.with(|n: &i32| n + 1);

    view! { cx,
        <p>{maybe}</p>
    }
}

v0.4.7

02 Aug 21:44
@gbj gbj
Compare
Choose a tag to compare

This is mostly a bug-fix release with a few small features. There will probably be one or two more patch releases before 0.5.0.

New Features

mut in component props

This didn't work before; now it does:

#[component]
fn TestMutCallback<'a, F>(
    cx: Scope,
    mut callback: F,
    value: &'a str,
) -> impl IntoView
where
    F: FnMut(u32) + 'static,
{

MaybeProp type

MaybeProp is essentially a wrapper for Option<MaybeSignal<Option<T>>> with a nicer API. This is useful for something like a component library, where you want to give a user the maximum flexibility in the types they give to props while still checking that they're the appropriate T.

This allows you to define optional props that

  1. may or may not be provided
  2. if provided, may be a plain value or a signal
  3. if a signal type, may or may not have a value (i.e., are a Signal<Option<T>>)

Implementing From<_> for a plain Signal<T> type here is possible, but depends on the changes in 0.5.0, so I'll add it for that release.

#[component]
pub fn App(cx: Scope) -> impl IntoView {
    let (count, set_count) = create_signal(cx, 5);

    view! { cx,
        <TakesMaybeProp/>
        <TakesMaybeProp maybe=42/>
        <TakesMaybeProp maybe=Signal::derive(cx, move || Some(count.get()))/>
    }
}

#[component]
pub fn TakesMaybeProp(cx: Scope, #[prop(optional, into)] maybe: MaybeProp<i32>) -> impl IntoView {
    // .get() gives you `Option<T>`, replacing `self.as_ref().and_then(|n| n.get())`
    let value: Option<i32> = maybe.get();
    // .with() takes a plain `&T`; returns `None` if the prop isn't present or the inner value is None
    let plus_one = maybe.with(|n: &i32| n + 1);

    view! { cx,
        <p>{maybe}</p>
    }
}

What's Changed

New Contributors

Full Changelog: v0.4.6...v.0.4.7