leptos/
show_let.rs

1use crate::{children::ViewFn, IntoView};
2use leptos_macro::component;
3use reactive_graph::traits::Get;
4use std::{marker::PhantomData, sync::Arc};
5use tachys::either::Either;
6
7/// Like `<Show>` but for `Option`. This is a shortcut for
8///
9/// ```ignore
10/// value.map(|value| {
11///     view! { ... }
12/// })
13/// ```
14///
15/// If you specify a `fallback` it is equvalent to
16///
17/// ```ignore
18/// value
19///     .map(
20///         |value| children(value),
21///     )
22///     .unwrap_or_else(fallback)
23/// ```
24///
25/// ## Example
26///
27/// ```
28/// # use leptos::prelude::*;
29/// #
30/// # #[component]
31/// # pub fn Example() -> impl IntoView {
32/// let (opt_value, set_opt_value) = signal(None::<i32>);
33///
34/// view! {
35///     <ShowLet some=opt_value let:value>
36///         "We have a value: " {value}
37///     </ShowLet>
38/// }
39/// # }
40/// ```
41///
42/// You can also specify a fallback:
43/// ```
44/// # use leptos::prelude::*;
45/// #
46/// # #[component]
47/// # pub fn Example() -> impl IntoView {
48/// let (opt_value, set_opt_value) = signal(None::<i32>);
49///
50/// view! {
51///     <ShowLet some=opt_value let:value fallback=|| "Got nothing">
52///         "We have a value: " {value}
53///     </ShowLet>
54/// }
55/// # }
56/// ```
57///
58/// In addition to signals you can also use a closure that returns an `Option`:
59///
60/// ```
61/// # use leptos::prelude::*;
62/// #
63/// # #[component]
64/// # pub fn Example() -> impl IntoView {
65/// let (opt_value, set_opt_value) = signal(None::<i32>);
66///
67/// view! {
68///     <ShowLet some=move || opt_value.get().map(|v| v * 2) let:value>
69///         "We have a value: " {value}
70///     </ShowLet>
71/// }
72/// # }
73/// ```
74#[component]
75pub fn ShowLet<T, ChFn, V, M>(
76    /// The children will be shown whenever `value` is `Some`.
77    ///
78    /// They take the inner value as an argument. Use `let:` to bind the value to a variable.
79    children: ChFn,
80
81    /// A signal of type `Option` or a closure that returns an `Option`.
82    /// If the value is `Some`, the children will be shown.
83    /// Otherwise the fallback will be shown, if present.
84    some: impl IntoOptionGetter<T, M>,
85
86    /// A closure that returns what gets rendered when the value is `None`.
87    /// By default this is the empty view.
88    ///
89    /// You can think of it as the closure inside `.unwrap_or_else(|| fallback())`.
90    #[prop(optional, into)]
91    fallback: ViewFn,
92
93    /// Marker for generic parameters. Ignore this.
94    #[prop(optional)]
95    _marker: PhantomData<(T, M)>,
96) -> impl IntoView
97where
98    ChFn: Fn(T) -> V + Send + Clone + 'static,
99    V: IntoView + 'static,
100    T: 'static,
101{
102    let getter = some.into_option_getter();
103
104    move || {
105        let children = children.clone();
106        let fallback = fallback.clone();
107
108        getter
109            .run()
110            .map(move |t| Either::Left(children(t)))
111            .unwrap_or_else(move || Either::Right(fallback.run()))
112    }
113}
114
115/// Servers as a wrapper for both, an `Option` signal or a closure that returns an `Option`.
116pub struct OptionGetter<T>(Arc<dyn Fn() -> Option<T> + Send + Sync + 'static>);
117
118impl<T> Clone for OptionGetter<T> {
119    fn clone(&self) -> Self {
120        Self(Arc::clone(&self.0))
121    }
122}
123
124impl<T> OptionGetter<T> {
125    /// Runs the getter and returns the result.
126    pub fn run(&self) -> Option<T> {
127        (self.0)()
128    }
129}
130
131/// Conversion trait for creating an `OptionGetter` from a closure or a signal.
132pub trait IntoOptionGetter<T, M> {
133    /// Converts the given value into an `OptionGetter`.
134    fn into_option_getter(self) -> OptionGetter<T>;
135}
136
137/// Marker type for creating an `OptionGetter` from a closure.
138/// Used so that the compiler doesn't complain about double implementations of the trait `IntoOptionGetter`.
139pub struct FunctionMarker;
140
141impl<T, F> IntoOptionGetter<T, FunctionMarker> for F
142where
143    F: Fn() -> Option<T> + Send + Sync + 'static,
144{
145    fn into_option_getter(self) -> OptionGetter<T> {
146        OptionGetter(Arc::new(self))
147    }
148}
149
150/// Marker type for creating an `OptionGetter` from a signal.
151/// Used so that the compiler doesn't complain about double implementations of the trait `IntoOptionGetter`.
152pub struct SignalMarker;
153
154impl<T, S> IntoOptionGetter<T, SignalMarker> for S
155where
156    S: Get<Value = Option<T>> + Clone + Send + Sync + 'static,
157{
158    fn into_option_getter(self) -> OptionGetter<T> {
159        let cloned = self.clone();
160        OptionGetter(Arc::new(move || cloned.get()))
161    }
162}